Commit

Author:

Hash:

Timestamp:

+3429 -4632 +/-133 browse

Kevin Schoon [me@kevinschoon.com]

5c5e94b2eff4d4181bd59ed359dabb509b0a34fc

Fri, 02 May 2025 14:05:37 +0000 (6 months ago)

replace tera with askama and eliminate all unused features
replace tera with askama and eliminate all unused features

Originally Askama was used as the template engine in Ayllu and then it was
replaced with Tera so that themes packages could change page layouts. This
was interesting but too complicated and so I've reverted back to using Askama.

All theme customization is handled by simple CSS files now and two example
themes: nord and catppuccin have been added. Nord is now the default theme
used by Ayllu.

Reverting back to Askama required many refactorings and all unused features
have been eliminated from the UI. The blame page has also been eliminated
since it never looked very good and I don't really have the skills to implement
a nice UI/UX for it.

The dependency on npm to download the open-props package has been eliminated
and the site uses all custom CSS. The lightningcss build.rs script has also
been eliminated and we simply concatentate a base CSS file and theme CSS file
together. A future change should try to minimize the CSS but the whole thing
is like 20kb and so I don't really care.

Stateful services, Maitred SMTP, and the Papyri container registry will
continue to be developed separately until they're completely ready to be
integrated into Ayllu.
1diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md
2index f173e9f..c7610e6 100644
3--- a/ATTRIBUTIONS.md
4+++ b/ATTRIBUTIONS.md
5 @@ -3,25 +3,18 @@
6 Ayllu would not be possible without many free software projects.
7
8 [Git](https://git-scm.com/)
9- [Meli](https://meli-email.org/)
10- [Mailpot](https://git.meli-email.org/meli/mailpot)
11
12 ## Many [Rust](https://www.rust-lang.org/) Libraries
13
14- * [tide](https://github.com/http-rs/tide)
15+ * [axum](https://github.com/tokio-rs/axum)
16 * [sqlx](https://github.com/launchbadge/sqlx/)
17 * [git2-rs](https://github.com/rust-lang/git2-rs)
18 * [tree-sitter](https://github.com/tree-sitter/tree-sitter)
19 * [comrak](https://github.com/kivikakk/comrak)
20 * [tokei](https://github.com/XAMPPRocky/tokei/)
21- * [lightningcss](https://docs.rs/lightningcss/latest/lightningcss/index.html)
22
23 And many more, see the `Cargo.toml` file.
24
25- ##### MIT
26-
27- [open-props.style](https://open-props.style/) used for base CSS and normalizing.
28-
29 ##### WTFPL
30
31 The "smart git" http backend is inspired / modified from [rgit](https://github.com/w4/rgit).
32 diff --git a/Cargo.lock b/Cargo.lock
33index de32164..8abb4e1 100644
34--- a/Cargo.lock
35+++ b/Cargo.lock
36 @@ -30,17 +30,6 @@ dependencies = [
37
38 [[package]]
39 name = "ahash"
40- version = "0.7.8"
41- source = "registry+https://github.com/rust-lang/crates.io-index"
42- checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
43- dependencies = [
44- "getrandom 0.2.15",
45- "once_cell",
46- "version_check",
47- ]
48-
49- [[package]]
50- name = "ahash"
51 version = "0.8.11"
52 source = "registry+https://github.com/rust-lang/crates.io-index"
53 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
54 @@ -172,6 +161,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
55 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
56
57 [[package]]
58+ name = "askama"
59+ version = "0.14.0"
60+ source = "registry+https://github.com/rust-lang/crates.io-index"
61+ checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
62+ dependencies = [
63+ "askama_derive",
64+ "itoa",
65+ "percent-encoding",
66+ "serde",
67+ "serde_json",
68+ ]
69+
70+ [[package]]
71+ name = "askama_derive"
72+ version = "0.14.0"
73+ source = "registry+https://github.com/rust-lang/crates.io-index"
74+ checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
75+ dependencies = [
76+ "askama_parser",
77+ "basic-toml",
78+ "memchr",
79+ "proc-macro2",
80+ "quote",
81+ "rustc-hash 2.1.1",
82+ "serde",
83+ "serde_derive",
84+ "syn",
85+ ]
86+
87+ [[package]]
88+ name = "askama_parser"
89+ version = "0.14.0"
90+ source = "registry+https://github.com/rust-lang/crates.io-index"
91+ checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
92+ dependencies = [
93+ "memchr",
94+ "serde",
95+ "serde_derive",
96+ "winnow",
97+ ]
98+
99+ [[package]]
100 name = "async-convert"
101 version = "1.0.0"
102 source = "registry+https://github.com/rust-lang/crates.io-index"
103 @@ -188,7 +219,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
104 dependencies = [
105 "proc-macro2",
106 "quote",
107- "syn 2.0.100",
108+ "syn",
109 ]
110
111 [[package]]
112 @@ -351,7 +382,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
113 dependencies = [
114 "proc-macro2",
115 "quote",
116- "syn 2.0.100",
117+ "syn",
118 ]
119
120 [[package]]
121 @@ -359,6 +390,7 @@ name = "ayllu"
122 version = "0.2.1"
123 dependencies = [
124 "anyhow",
125+ "askama",
126 "async-trait",
127 "axum",
128 "axum-extra",
129 @@ -377,7 +409,6 @@ dependencies = [
130 "include_dir",
131 "lazy_static",
132 "libloading",
133- "lightningcss",
134 "log",
135 "mime",
136 "mime_guess",
137 @@ -388,10 +419,10 @@ dependencies = [
138 "serde",
139 "serde_json",
140 "tabwriter",
141- "tera",
142 "thiserror 2.0.12",
143 "time",
144 "time-macros",
145+ "timeutil",
146 "tokei",
147 "tokio",
148 "tokio-stream",
149 @@ -566,21 +597,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
150 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
151
152 [[package]]
153- name = "base64-simd"
154- version = "0.7.0"
155- source = "registry+https://github.com/rust-lang/crates.io-index"
156- checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5"
157- dependencies = [
158- "simd-abstraction",
159- ]
160-
161- [[package]]
162 name = "base64ct"
163 version = "1.6.0"
164 source = "registry+https://github.com/rust-lang/crates.io-index"
165 checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
166
167 [[package]]
168+ name = "basic-toml"
169+ version = "0.1.10"
170+ source = "registry+https://github.com/rust-lang/crates.io-index"
171+ checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
172+ dependencies = [
173+ "serde",
174+ ]
175+
176+ [[package]]
177 name = "bincode"
178 version = "1.3.3"
179 source = "registry+https://github.com/rust-lang/crates.io-index"
180 @@ -598,7 +629,7 @@ dependencies = [
181 "bitflags 2.9.0",
182 "cexpr",
183 "clang-sys",
184- "itertools 0.12.1",
185+ "itertools",
186 "lazy_static",
187 "lazycell",
188 "log",
189 @@ -608,7 +639,7 @@ dependencies = [
190 "regex",
191 "rustc-hash 1.1.0",
192 "shlex",
193- "syn 2.0.100",
194+ "syn",
195 "which",
196 ]
197
198 @@ -643,18 +674,6 @@ dependencies = [
199 ]
200
201 [[package]]
202- name = "bitvec"
203- version = "1.0.1"
204- source = "registry+https://github.com/rust-lang/crates.io-index"
205- checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
206- dependencies = [
207- "funty",
208- "radium",
209- "tap",
210- "wyz",
211- ]
212-
213- [[package]]
214 name = "block-buffer"
215 version = "0.10.4"
216 source = "registry+https://github.com/rust-lang/crates.io-index"
217 @@ -680,28 +699,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
218 checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
219
220 [[package]]
221- name = "bytecheck"
222- version = "0.6.12"
223- source = "registry+https://github.com/rust-lang/crates.io-index"
224- checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
225- dependencies = [
226- "bytecheck_derive",
227- "ptr_meta",
228- "simdutf8",
229- ]
230-
231- [[package]]
232- name = "bytecheck_derive"
233- version = "0.6.12"
234- source = "registry+https://github.com/rust-lang/crates.io-index"
235- checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
236- dependencies = [
237- "proc-macro2",
238- "quote",
239- "syn 1.0.109",
240- ]
241-
242- [[package]]
243 name = "bytemuck"
244 version = "1.21.0"
245 source = "registry+https://github.com/rust-lang/crates.io-index"
246 @@ -909,7 +906,7 @@ dependencies = [
247 "heck",
248 "proc-macro2",
249 "quote",
250- "syn 2.0.100",
251+ "syn",
252 ]
253
254 [[package]]
255 @@ -976,26 +973,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
256 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
257
258 [[package]]
259- name = "const-str"
260- version = "0.3.2"
261- source = "registry+https://github.com/rust-lang/crates.io-index"
262- checksum = "21077772762a1002bb421c3af42ac1725fa56066bfc53d9a55bb79905df2aaf3"
263- dependencies = [
264- "const-str-proc-macro",
265- ]
266-
267- [[package]]
268- name = "const-str-proc-macro"
269- version = "0.3.2"
270- source = "registry+https://github.com/rust-lang/crates.io-index"
271- checksum = "5e1e0fdd2e5d3041e530e1b21158aeeef8b5d0e306bc5c1e3d6cf0930d10e25a"
272- dependencies = [
273- "proc-macro2",
274- "quote",
275- "syn 1.0.109",
276- ]
277-
278- [[package]]
279 name = "constant_time_eq"
280 version = "0.3.1"
281 source = "registry+https://github.com/rust-lang/crates.io-index"
282 @@ -1160,38 +1137,6 @@ dependencies = [
283 ]
284
285 [[package]]
286- name = "cssparser"
287- version = "0.33.0"
288- source = "registry+https://github.com/rust-lang/crates.io-index"
289- checksum = "9be934d936a0fbed5bcdc01042b770de1398bf79d0e192f49fa7faea0e99281e"
290- dependencies = [
291- "cssparser-macros",
292- "dtoa-short",
293- "itoa",
294- "phf",
295- "smallvec",
296- ]
297-
298- [[package]]
299- name = "cssparser-color"
300- version = "0.1.0"
301- source = "registry+https://github.com/rust-lang/crates.io-index"
302- checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f"
303- dependencies = [
304- "cssparser",
305- ]
306-
307- [[package]]
308- name = "cssparser-macros"
309- version = "0.6.1"
310- source = "registry+https://github.com/rust-lang/crates.io-index"
311- checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
312- dependencies = [
313- "quote",
314- "syn 2.0.100",
315- ]
316-
317- [[package]]
318 name = "curve25519-dalek"
319 version = "4.1.3"
320 source = "registry+https://github.com/rust-lang/crates.io-index"
321 @@ -1215,7 +1160,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
322 dependencies = [
323 "proc-macro2",
324 "quote",
325- "syn 2.0.100",
326+ "syn",
327 ]
328
329 [[package]]
330 @@ -1239,7 +1184,7 @@ dependencies = [
331 "proc-macro2",
332 "quote",
333 "strsim 0.11.1",
334- "syn 2.0.100",
335+ "syn",
336 ]
337
338 [[package]]
339 @@ -1250,7 +1195,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
340 dependencies = [
341 "darling_core",
342 "quote",
343- "syn 2.0.100",
344+ "syn",
345 ]
346
347 [[package]]
348 @@ -1265,34 +1210,12 @@ dependencies = [
349 ]
350
351 [[package]]
352- name = "dashmap"
353- version = "5.5.3"
354- source = "registry+https://github.com/rust-lang/crates.io-index"
355- checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
356- dependencies = [
357- "cfg-if",
358- "hashbrown 0.14.5",
359- "lock_api",
360- "once_cell",
361- "parking_lot_core 0.9.10",
362- ]
363-
364- [[package]]
365 name = "data-encoding"
366 version = "2.8.0"
367 source = "registry+https://github.com/rust-lang/crates.io-index"
368 checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
369
370 [[package]]
371- name = "data-url"
372- version = "0.1.1"
373- source = "registry+https://github.com/rust-lang/crates.io-index"
374- checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193"
375- dependencies = [
376- "matches",
377- ]
378-
379- [[package]]
380 name = "deflate64"
381 version = "0.1.9"
382 source = "registry+https://github.com/rust-lang/crates.io-index"
383 @@ -1327,7 +1250,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
384 dependencies = [
385 "proc-macro2",
386 "quote",
387- "syn 2.0.100",
388+ "syn",
389 ]
390
391 [[package]]
392 @@ -1348,7 +1271,7 @@ dependencies = [
393 "darling",
394 "proc-macro2",
395 "quote",
396- "syn 2.0.100",
397+ "syn",
398 ]
399
400 [[package]]
401 @@ -1358,7 +1281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
402 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
403 dependencies = [
404 "derive_builder_core",
405- "syn 2.0.100",
406+ "syn",
407 ]
408
409 [[package]]
410 @@ -1437,7 +1360,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
411 dependencies = [
412 "proc-macro2",
413 "quote",
414- "syn 2.0.100",
415+ "syn",
416 ]
417
418 [[package]]
419 @@ -1465,21 +1388,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
420 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
421
422 [[package]]
423- name = "dtoa"
424- version = "1.0.10"
425- source = "registry+https://github.com/rust-lang/crates.io-index"
426- checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
427-
428- [[package]]
429- name = "dtoa-short"
430- version = "0.3.5"
431- source = "registry+https://github.com/rust-lang/crates.io-index"
432- checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
433- dependencies = [
434- "dtoa",
435- ]
436-
437- [[package]]
438 name = "dunce"
439 version = "1.0.5"
440 source = "registry+https://github.com/rust-lang/crates.io-index"
441 @@ -1582,7 +1490,7 @@ dependencies = [
442 "heck",
443 "proc-macro2",
444 "quote",
445- "syn 2.0.100",
446+ "syn",
447 ]
448
449 [[package]]
450 @@ -1767,7 +1675,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
451 dependencies = [
452 "proc-macro2",
453 "quote",
454- "syn 2.0.100",
455+ "syn",
456 ]
457
458 [[package]]
459 @@ -1809,12 +1717,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
460 checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
461
462 [[package]]
463- name = "funty"
464- version = "2.0.0"
465- source = "registry+https://github.com/rust-lang/crates.io-index"
466- checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
467-
468- [[package]]
469 name = "futures"
470 version = "0.3.31"
471 source = "registry+https://github.com/rust-lang/crates.io-index"
472 @@ -1881,7 +1783,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
473 dependencies = [
474 "proc-macro2",
475 "quote",
476- "syn 2.0.100",
477+ "syn",
478 ]
479
480 [[package]]
481 @@ -2086,9 +1988,6 @@ name = "hashbrown"
482 version = "0.12.3"
483 source = "registry+https://github.com/rust-lang/crates.io-index"
484 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
485- dependencies = [
486- "ahash 0.7.8",
487- ]
488
489 [[package]]
490 name = "hashbrown"
491 @@ -2096,7 +1995,7 @@ version = "0.14.5"
492 source = "registry+https://github.com/rust-lang/crates.io-index"
493 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
494 dependencies = [
495- "ahash 0.8.11",
496+ "ahash",
497 "allocator-api2",
498 ]
499
500 @@ -2119,7 +2018,7 @@ checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb"
501 dependencies = [
502 "proc-macro2",
503 "quote",
504- "syn 2.0.100",
505+ "syn",
506 ]
507
508 [[package]]
509 @@ -2554,7 +2453,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
510 dependencies = [
511 "proc-macro2",
512 "quote",
513- "syn 2.0.100",
514+ "syn",
515 ]
516
517 [[package]]
518 @@ -2700,15 +2599,6 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
519
520 [[package]]
521 name = "itertools"
522- version = "0.10.5"
523- source = "registry+https://github.com/rust-lang/crates.io-index"
524- checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
525- dependencies = [
526- "either",
527- ]
528-
529- [[package]]
530- name = "itertools"
531 version = "0.12.1"
532 source = "registry+https://github.com/rust-lang/crates.io-index"
533 checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
534 @@ -2765,7 +2655,7 @@ dependencies = [
535 "convert_case",
536 "proc-macro2",
537 "quote",
538- "syn 2.0.100",
539+ "syn",
540 ]
541
542 [[package]]
543 @@ -2901,45 +2791,6 @@ dependencies = [
544 ]
545
546 [[package]]
547- name = "lightningcss"
548- version = "1.0.0-alpha.65"
549- source = "registry+https://github.com/rust-lang/crates.io-index"
550- checksum = "c84f971730745f4aaac013b6cf4328baf1548efc973c0d95cfd843a3c1ca07af"
551- dependencies = [
552- "ahash 0.8.11",
553- "bitflags 2.9.0",
554- "const-str",
555- "cssparser",
556- "cssparser-color",
557- "dashmap 5.5.3",
558- "data-encoding",
559- "getrandom 0.2.15",
560- "indexmap 2.7.1",
561- "itertools 0.10.5",
562- "lazy_static",
563- "lightningcss-derive",
564- "parcel_selectors",
565- "parcel_sourcemap",
566- "paste",
567- "pathdiff",
568- "rayon",
569- "serde",
570- "smallvec",
571- ]
572-
573- [[package]]
574- name = "lightningcss-derive"
575- version = "1.0.0-alpha.43"
576- source = "registry+https://github.com/rust-lang/crates.io-index"
577- checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252"
578- dependencies = [
579- "convert_case",
580- "proc-macro2",
581- "quote",
582- "syn 1.0.109",
583- ]
584-
585- [[package]]
586 name = "linked-hash-map"
587 version = "0.5.6"
588 source = "registry+https://github.com/rust-lang/crates.io-index"
589 @@ -3016,7 +2867,7 @@ version = "0.6.1"
590 source = "registry+https://github.com/rust-lang/crates.io-index"
591 checksum = "971b381e86bc19e81d7f4b5ffa3328a15fb7e2998fec82a82f4f144e65c73f5f"
592 dependencies = [
593- "ahash 0.8.11",
594+ "ahash",
595 "flate2",
596 "hickory-resolver",
597 "mail-builder 0.4.1",
598 @@ -3135,12 +2986,6 @@ dependencies = [
599 ]
600
601 [[package]]
602- name = "matches"
603- version = "0.1.10"
604- source = "registry+https://github.com/rust-lang/crates.io-index"
605- checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
606-
607- [[package]]
608 name = "matchit"
609 version = "0.7.3"
610 source = "registry+https://github.com/rust-lang/crates.io-index"
611 @@ -3373,7 +3218,7 @@ dependencies = [
612 "proc-macro2",
613 "quote",
614 "rustc_version",
615- "syn 2.0.100",
616+ "syn",
617 "urlencoding",
618 ]
619
620 @@ -3437,7 +3282,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
621 dependencies = [
622 "proc-macro2",
623 "quote",
624- "syn 2.0.100",
625+ "syn",
626 ]
627
628 [[package]]
629 @@ -3465,48 +3310,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
630 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
631
632 [[package]]
633- name = "outref"
634- version = "0.1.0"
635- source = "registry+https://github.com/rust-lang/crates.io-index"
636- checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
637-
638- [[package]]
639 name = "overload"
640 version = "0.1.1"
641 source = "registry+https://github.com/rust-lang/crates.io-index"
642 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
643
644 [[package]]
645- name = "parcel_selectors"
646- version = "0.28.1"
647- source = "registry+https://github.com/rust-lang/crates.io-index"
648- checksum = "dccbc6fb560df303a44e511618256029410efbc87779018f751ef12c488271fe"
649- dependencies = [
650- "bitflags 2.9.0",
651- "cssparser",
652- "log",
653- "phf",
654- "phf_codegen",
655- "precomputed-hash",
656- "rustc-hash 2.1.1",
657- "smallvec",
658- ]
659-
660- [[package]]
661- name = "parcel_sourcemap"
662- version = "2.1.1"
663- source = "registry+https://github.com/rust-lang/crates.io-index"
664- checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb"
665- dependencies = [
666- "base64-simd",
667- "data-url",
668- "rkyv",
669- "serde",
670- "serde_json",
671- "vlq",
672- ]
673-
674- [[package]]
675 name = "parking"
676 version = "2.2.1"
677 source = "registry+https://github.com/rust-lang/crates.io-index"
678 @@ -3576,12 +3385,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
679 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
680
681 [[package]]
682- name = "pathdiff"
683- version = "0.2.3"
684- source = "registry+https://github.com/rust-lang/crates.io-index"
685- checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
686-
687- [[package]]
688 name = "pathfinder_geometry"
689 version = "0.5.1"
690 source = "registry+https://github.com/rust-lang/crates.io-index"
691 @@ -3656,7 +3459,7 @@ dependencies = [
692 "pest_meta",
693 "proc-macro2",
694 "quote",
695- "syn 2.0.100",
696+ "syn",
697 ]
698
699 [[package]]
700 @@ -3676,7 +3479,6 @@ version = "0.11.3"
701 source = "registry+https://github.com/rust-lang/crates.io-index"
702 checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
703 dependencies = [
704- "phf_macros",
705 "phf_shared",
706 ]
707
708 @@ -3701,19 +3503,6 @@ dependencies = [
709 ]
710
711 [[package]]
712- name = "phf_macros"
713- version = "0.11.3"
714- source = "registry+https://github.com/rust-lang/crates.io-index"
715- checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
716- dependencies = [
717- "phf_generator",
718- "phf_shared",
719- "proc-macro2",
720- "quote",
721- "syn 2.0.100",
722- ]
723-
724- [[package]]
725 name = "phf_shared"
726 version = "0.11.3"
727 source = "registry+https://github.com/rust-lang/crates.io-index"
728 @@ -3849,19 +3638,13 @@ dependencies = [
729 ]
730
731 [[package]]
732- name = "precomputed-hash"
733- version = "0.1.1"
734- source = "registry+https://github.com/rust-lang/crates.io-index"
735- checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
736-
737- [[package]]
738 name = "prettyplease"
739 version = "0.2.29"
740 source = "registry+https://github.com/rust-lang/crates.io-index"
741 checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
742 dependencies = [
743 "proc-macro2",
744- "syn 2.0.100",
745+ "syn",
746 ]
747
748 [[package]]
749 @@ -3889,26 +3672,6 @@ dependencies = [
750 ]
751
752 [[package]]
753- name = "ptr_meta"
754- version = "0.1.4"
755- source = "registry+https://github.com/rust-lang/crates.io-index"
756- checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
757- dependencies = [
758- "ptr_meta_derive",
759- ]
760-
761- [[package]]
762- name = "ptr_meta_derive"
763- version = "0.1.4"
764- source = "registry+https://github.com/rust-lang/crates.io-index"
765- checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
766- dependencies = [
767- "proc-macro2",
768- "quote",
769- "syn 1.0.109",
770- ]
771-
772- [[package]]
773 name = "quick-error"
774 version = "1.2.3"
775 source = "registry+https://github.com/rust-lang/crates.io-index"
776 @@ -3939,7 +3702,7 @@ version = "0.6.10"
777 source = "registry+https://github.com/rust-lang/crates.io-index"
778 checksum = "3f67cfc9c723c39f3615eb0840b00c4cb9e2b068d2fa761a30d845ec91730a59"
779 dependencies = [
780- "ahash 0.8.11",
781+ "ahash",
782 "equivalent",
783 "hashbrown 0.14.5",
784 "parking_lot 0.12.3",
785 @@ -3980,12 +3743,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
786 checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
787
788 [[package]]
789- name = "radium"
790- version = "0.7.0"
791- source = "registry+https://github.com/rust-lang/crates.io-index"
792- checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
793-
794- [[package]]
795 name = "rand"
796 version = "0.8.5"
797 source = "registry+https://github.com/rust-lang/crates.io-index"
798 @@ -4109,15 +3866,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
799 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
800
801 [[package]]
802- name = "rend"
803- version = "0.4.2"
804- source = "registry+https://github.com/rust-lang/crates.io-index"
805- checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
806- dependencies = [
807- "bytecheck",
808- ]
809-
810- [[package]]
811 name = "reqwest"
812 version = "0.12.15"
813 source = "registry+https://github.com/rust-lang/crates.io-index"
814 @@ -4201,35 +3949,6 @@ dependencies = [
815 ]
816
817 [[package]]
818- name = "rkyv"
819- version = "0.7.45"
820- source = "registry+https://github.com/rust-lang/crates.io-index"
821- checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
822- dependencies = [
823- "bitvec",
824- "bytecheck",
825- "bytes",
826- "hashbrown 0.12.3",
827- "ptr_meta",
828- "rend",
829- "rkyv_derive",
830- "seahash",
831- "tinyvec",
832- "uuid",
833- ]
834-
835- [[package]]
836- name = "rkyv_derive"
837- version = "0.7.45"
838- source = "registry+https://github.com/rust-lang/crates.io-index"
839- checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
840- dependencies = [
841- "proc-macro2",
842- "quote",
843- "syn 1.0.109",
844- ]
845-
846- [[package]]
847 name = "rsa"
848 version = "0.9.7"
849 source = "registry+https://github.com/rust-lang/crates.io-index"
850 @@ -4435,12 +4154,6 @@ dependencies = [
851 ]
852
853 [[package]]
854- name = "seahash"
855- version = "4.1.0"
856- source = "registry+https://github.com/rust-lang/crates.io-index"
857- checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
858-
859- [[package]]
860 name = "security-framework"
861 version = "2.11.1"
862 source = "registry+https://github.com/rust-lang/crates.io-index"
863 @@ -4486,7 +4199,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
864 dependencies = [
865 "proc-macro2",
866 "quote",
867- "syn 2.0.100",
868+ "syn",
869 ]
870
871 [[package]]
872 @@ -4572,7 +4285,7 @@ dependencies = [
873 "darling",
874 "proc-macro2",
875 "quote",
876- "syn 2.0.100",
877+ "syn",
878 ]
879
880 [[package]]
881 @@ -4638,27 +4351,12 @@ dependencies = [
882 ]
883
884 [[package]]
885- name = "simd-abstraction"
886- version = "0.7.1"
887- source = "registry+https://github.com/rust-lang/crates.io-index"
888- checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987"
889- dependencies = [
890- "outref",
891- ]
892-
893- [[package]]
894 name = "simd-adler32"
895 version = "0.3.7"
896 source = "registry+https://github.com/rust-lang/crates.io-index"
897 checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
898
899 [[package]]
900- name = "simdutf8"
901- version = "0.1.5"
902- source = "registry+https://github.com/rust-lang/crates.io-index"
903- checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
904-
905- [[package]]
906 name = "siphasher"
907 version = "1.0.1"
908 source = "registry+https://github.com/rust-lang/crates.io-index"
909 @@ -4796,7 +4494,7 @@ dependencies = [
910 "quote",
911 "sqlx-core",
912 "sqlx-macros-core",
913- "syn 2.0.100",
914+ "syn",
915 ]
916
917 [[package]]
918 @@ -4819,7 +4517,7 @@ dependencies = [
919 "sqlx-mysql",
920 "sqlx-postgres",
921 "sqlx-sqlite",
922- "syn 2.0.100",
923+ "syn",
924 "tempfile",
925 "tokio",
926 "url",
927 @@ -4987,17 +4685,6 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
928
929 [[package]]
930 name = "syn"
931- version = "1.0.109"
932- source = "registry+https://github.com/rust-lang/crates.io-index"
933- checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
934- dependencies = [
935- "proc-macro2",
936- "quote",
937- "unicode-ident",
938- ]
939-
940- [[package]]
941- name = "syn"
942 version = "2.0.100"
943 source = "registry+https://github.com/rust-lang/crates.io-index"
944 checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
945 @@ -5024,7 +4711,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
946 dependencies = [
947 "proc-macro2",
948 "quote",
949- "syn 2.0.100",
950+ "syn",
951 ]
952
953 [[package]]
954 @@ -5081,12 +4768,6 @@ dependencies = [
955 ]
956
957 [[package]]
958- name = "tap"
959- version = "1.0.1"
960- source = "registry+https://github.com/rust-lang/crates.io-index"
961- checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
962-
963- [[package]]
964 name = "tempfile"
965 version = "3.19.1"
966 source = "registry+https://github.com/rust-lang/crates.io-index"
967 @@ -5185,7 +4866,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
968 dependencies = [
969 "proc-macro2",
970 "quote",
971- "syn 2.0.100",
972+ "syn",
973 ]
974
975 [[package]]
976 @@ -5196,7 +4877,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
977 dependencies = [
978 "proc-macro2",
979 "quote",
980- "syn 2.0.100",
981+ "syn",
982 ]
983
984 [[package]]
985 @@ -5243,6 +4924,9 @@ dependencies = [
986 [[package]]
987 name = "timeutil"
988 version = "0.1.0"
989+ dependencies = [
990+ "time",
991+ ]
992
993 [[package]]
994 name = "tinystr"
995 @@ -5278,7 +4962,7 @@ dependencies = [
996 "aho-corasick 0.7.20",
997 "clap 2.34.0",
998 "crossbeam-channel",
999- "dashmap 4.0.2",
1000+ "dashmap",
1001 "dirs 3.0.2",
1002 "encoding_rs_io",
1003 "env_logger",
1004 @@ -5323,7 +5007,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
1005 dependencies = [
1006 "proc-macro2",
1007 "quote",
1008- "syn 2.0.100",
1009+ "syn",
1010 ]
1011
1012 [[package]]
1013 @@ -5499,7 +5183,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
1014 dependencies = [
1015 "proc-macro2",
1016 "quote",
1017- "syn 2.0.100",
1018+ "syn",
1019 ]
1020
1021 [[package]]
1022 @@ -5759,12 +5443,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1023 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1024
1025 [[package]]
1026- name = "uuid"
1027- version = "1.16.0"
1028- source = "registry+https://github.com/rust-lang/crates.io-index"
1029- checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
1030-
1031- [[package]]
1032 name = "valuable"
1033 version = "0.1.1"
1034 source = "registry+https://github.com/rust-lang/crates.io-index"
1035 @@ -5789,12 +5467,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1036 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1037
1038 [[package]]
1039- name = "vlq"
1040- version = "0.5.1"
1041- source = "registry+https://github.com/rust-lang/crates.io-index"
1042- checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff"
1043-
1044- [[package]]
1045 name = "walkdir"
1046 version = "2.5.0"
1047 source = "registry+https://github.com/rust-lang/crates.io-index"
1048 @@ -5856,7 +5528,7 @@ dependencies = [
1049 "log",
1050 "proc-macro2",
1051 "quote",
1052- "syn 2.0.100",
1053+ "syn",
1054 "wasm-bindgen-shared",
1055 ]
1056
1057 @@ -5891,7 +5563,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
1058 dependencies = [
1059 "proc-macro2",
1060 "quote",
1061- "syn 2.0.100",
1062+ "syn",
1063 "wasm-bindgen-backend",
1064 "wasm-bindgen-shared",
1065 ]
1066 @@ -6326,15 +5998,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1067 checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
1068
1069 [[package]]
1070- name = "wyz"
1071- version = "0.5.1"
1072- source = "registry+https://github.com/rust-lang/crates.io-index"
1073- checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
1074- dependencies = [
1075- "tap",
1076- ]
1077-
1078- [[package]]
1079 name = "xdg"
1080 version = "2.5.2"
1081 source = "registry+https://github.com/rust-lang/crates.io-index"
1082 @@ -6380,7 +6043,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
1083 dependencies = [
1084 "proc-macro2",
1085 "quote",
1086- "syn 2.0.100",
1087+ "syn",
1088 "synstructure",
1089 ]
1090
1091 @@ -6402,7 +6065,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
1092 dependencies = [
1093 "proc-macro2",
1094 "quote",
1095- "syn 2.0.100",
1096+ "syn",
1097 ]
1098
1099 [[package]]
1100 @@ -6422,7 +6085,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
1101 dependencies = [
1102 "proc-macro2",
1103 "quote",
1104- "syn 2.0.100",
1105+ "syn",
1106 "synstructure",
1107 ]
1108
1109 @@ -6443,7 +6106,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
1110 dependencies = [
1111 "proc-macro2",
1112 "quote",
1113- "syn 2.0.100",
1114+ "syn",
1115 ]
1116
1117 [[package]]
1118 @@ -6465,7 +6128,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
1119 dependencies = [
1120 "proc-macro2",
1121 "quote",
1122- "syn 2.0.100",
1123+ "syn",
1124 ]
1125
1126 [[package]]
1127 diff --git a/ayllu/Cargo.toml b/ayllu/Cargo.toml
1128index ab43c22..eb4e431 100644
1129--- a/ayllu/Cargo.toml
1130+++ b/ayllu/Cargo.toml
1131 @@ -2,7 +2,7 @@
1132 name = "ayllu"
1133 version = "0.2.1"
1134 edition = "2021"
1135- rust-version = "1.70.0"
1136+ rust-version = "1.86.0"
1137
1138 [[bin]]
1139 name = "ayllu"
1140 @@ -11,6 +11,7 @@ name = "ayllu"
1141 ayllu_api = { path = "../crates/api" }
1142 ayllu_git = { path = "../crates/git" }
1143 ayllu_config = { path = "../crates/config" }
1144+ timeutil = {path = "../crates/timeutil"}
1145
1146 async-trait = { workspace = true }
1147 bytes = { workspace = true }
1148 @@ -33,7 +34,6 @@ tokei = "12.1.2"
1149 time = "0.3.41"
1150 time-macros = "0.2.22"
1151 file-mode = "0.1.2"
1152- tera = "1.20.0"
1153 lazy_static = "1.5.0"
1154 mime_guess = "2.0.5"
1155 serde_json = "1.0.140"
1156 @@ -57,7 +57,7 @@ include_dir = { version = "0.7.4", features = ["glob"] }
1157 webfinger-rs = { version = "0.0.12", features = ["axum"] }
1158
1159 quick-xml = { version = "0.37.5", features = ["encoding"] }
1160+ askama = { version = "0.14.0" }
1161
1162 [build-dependencies]
1163 cc="*"
1164- lightningcss = "1.0.0-alpha.65"
1165 diff --git a/ayllu/assets/anchor.svg b/ayllu/assets/anchor.svg
1166new file mode 100644
1167index 0000000..62454e3
1168--- /dev/null
1169+++ b/ayllu/assets/anchor.svg
1170 @@ -0,0 +1 @@
1171+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon" width="24" height="24" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .75.75 0 0 1 .018-1.042.75.75 0 0 1 1.042-.018 2 2 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.75.75 0 0 1-1.042-.018.75.75 0 0 1-.018-1.042m-4.69 9.64a2 2 0 0 0 2.83 0l1.25-1.25a.75.75 0 0 1 1.042.018.75.75 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .75.75 0 0 1-.018 1.042.75.75 0 0 1-1.042.018 2 2 0 0 0-2.83 0l-2.5 2.5a2 2 0 0 0 0 2.83"></path></svg>
1172 diff --git a/ayllu/assets/books.svg b/ayllu/assets/books.svg
1173new file mode 100644
1174index 0000000..ba393a5
1175--- /dev/null
1176+++ b/ayllu/assets/books.svg
1177 @@ -0,0 +1,16 @@
1178+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1179+ <g id="color">
1180+ <path fill="#fff" d="M61.2167,21.6411,37.911,15.0386a1.0068,1.0068,0,0,0-.5537.0019l-22.5947,6.627a.9484.9484,0,0,0-.1365.0719,5.3829,5.3829,0,0,0-1.8343,9.5426,5.3642,5.3642,0,0,0,.0663,8.6968,5.3776,5.3776,0,0,0,1.4856,9.5022l23.0146,6.7128a1.0034,1.0034,0,0,0,.5547.002l23.3057-6.6514a1.0008,1.0008,0,0,0,.7255-.9619V22.603A1,1,0,0,0,61.2167,21.6411Z"/>
1181+ <polygon fill="#92d3f5" points="37.638 15.976 60.944 22.579 37.638 29.231 15.044 22.603 37.638 15.976"/>
1182+ <path fill="#61b2e4" d="M15.0823,24.7275,37.0756,31.174a2,2,0,0,0,1.1113.0039l23.2947-6.7653a.613.613,0,0,0,.442-.5794l.0189-1.2508L37.6381,29.2547,15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2423a.6255.6255,0,0,0-.7968-.6215L37.6442,35.7552l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
1183+ <path fill="#d22f27" d="M15.0823,33.4092l21.9933,6.4465a1.9988,1.9988,0,0,0,1.1113.004l23.2947-6.7653a.6131.6131,0,0,0,.442-.5794l.0189-1.2508L37.6381,37.9365,15.08,31.3243a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,44.437l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
1184+ <path fill="#5c9e31" d="M15.0823,42.1549l21.9933,6.4465a2,2,0,0,0,1.1113.0039L61.4816,41.84a.613.613,0,0,0,.442-.5794l.0189-1.2507L37.6381,46.6821,15.08,40.07a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,53.1827l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
1185+ <polygon fill="#61b2e4" points="44.32 17.794 38.594 16.172 16 22.799 21.81 24.504 44.32 17.794"/>
1186+ </g>
1187+ <g id="line">
1188+ <polygon fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="37.638 16 60.944 22.603 37.638 29.255 15.044 22.627 37.638 16"/>
1189+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
1190+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,31.2942a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
1191+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,40.04a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
1192+ </g>
1193+ </svg>
1194 diff --git a/ayllu/assets/building.svg b/ayllu/assets/building.svg
1195new file mode 100644
1196index 0000000..f57946b
1197--- /dev/null
1198+++ b/ayllu/assets/building.svg
1199 @@ -0,0 +1,26 @@
1200+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1201+ <g id="color">
1202+ <rect x="18.54" y="26" width="34.92" height="25.95" fill="#9b9b9a"/>
1203+ <rect x="12" y="56" width="48" height="4" fill="#fff" stroke-miterlimit="10"/>
1204+ <rect x="14.13" y="22" width="43.74" height="4" fill="#fff" stroke-miterlimit="10"/>
1205+ <rect x="15.72" y="52" width="40.55" height="4" fill="#fff" stroke-miterlimit="10"/>
1206+ <rect x="18.5" y="26" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
1207+ <rect x="27.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
1208+ <rect x="49.52" y="25.95" width="3.941" height="26" fill="#fff" stroke-miterlimit="10"/>
1209+ <polygon fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="36 12 19 21.75 53 21.75"/>
1210+ <rect x="40.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
1211+ </g>
1212+ <g id="hair"/>
1213+ <g id="skin"/>
1214+ <g id="skin-shadow"/>
1215+ <g id="line">
1216+ <rect x="11.99" y="55.89" width="48.01" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1217+ <rect x="14.18" y="21.76" width="43.65" height="3.999" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.001"/>
1218+ <rect x="15.27" y="51.76" width="41.47" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1219+ <rect x="18.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
1220+ <rect x="27.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
1221+ <rect x="49.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
1222+ <polygon transform="matrix(1.091 0 0 .9982 -3.283 .098)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.916" points="36 12 19 21.75 53 21.75"/>
1223+ <rect x="40.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
1224+ </g>
1225+ </svg>
1226 diff --git a/ayllu/assets/chat.svg b/ayllu/assets/chat.svg
1227new file mode 100644
1228index 0000000..e36c2f6
1229--- /dev/null
1230+++ b/ayllu/assets/chat.svg
1231 @@ -0,0 +1,16 @@
1232+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1233+ <g id="color">
1234+ <path fill="#D0CFCE" stroke="none" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527 l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
1235+ <path fill="#9B9B9A" stroke="none" d="M60.5096,60.5444c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077 c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351 L60.5096,60.5444z"/>
1236+ </g>
1237+ <g id="hair"/>
1238+ <g id="skin"/>
1239+ <g id="skin-shadow"/>
1240+ <g id="line">
1241+ <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841 l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557 c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
1242+ <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M60.5096,60.5444 c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4 v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351L60.5096,60.5444z"/>
1243+ <circle cx="31.9965" cy="48.0593" r="2" fill="#000000" stroke="none"/>
1244+ <circle cx="40.0081" cy="48.0593" r="2" fill="#000000" stroke="none"/>
1245+ <circle cx="48.0198" cy="48.0593" r="2" fill="#000000" stroke="none"/>
1246+ </g>
1247+ </svg>
1248 diff --git a/ayllu/assets/check.svg b/ayllu/assets/check.svg
1249new file mode 100644
1250index 0000000..631b6ab
1251--- /dev/null
1252+++ b/ayllu/assets/check.svg
1253 @@ -0,0 +1,8 @@
1254+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1255+ <g id="color">
1256+ <path fill="#b1cc33" d="m61.5 23.3-8.013-8.013-25.71 25.71-9.26-9.26-8.013 8.013 17.42 17.44z"/>
1257+ </g>
1258+ <g id="line">
1259+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="m10.5 39.76 17.42 17.44 33.58-33.89-8.013-8.013-25.71 25.71-9.26-9.26z"/>
1260+ </g>
1261+ </svg>
1262 diff --git a/ayllu/assets/code.svg b/ayllu/assets/code.svg
1263new file mode 100644
1264index 0000000..f92f207
1265--- /dev/null
1266+++ b/ayllu/assets/code.svg
1267 @@ -0,0 +1,17 @@
1268+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1269+ <g id="color">
1270+ <rect x="11" y="16.0833" width="50" height="39.8333" fill="#d0cfce" stroke="none"/>
1271+ </g>
1272+ <g id="hair"/>
1273+ <g id="skin"/>
1274+ <g id="skin-shadow"/>
1275+ <g id="line">
1276+ <rect x="11" y="16.0009" width="50" height="39.9982" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1277+ <polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="16.3287,16.4792 16.3287,20.8542 11,20.8542 61,20.8542"/>
1278+ <line x1="28.8333" x2="21.9062" y1="30.3947" y2="37.3218" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1279+ <line x1="28.8333" x2="21.9062" y1="44.3166" y2="37.3895" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1280+ <line x1="38.1836" x2="32.8086" y1="28.1523" y2="46.25" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1281+ <line x1="42.1588" x2="49.0859" y1="44.2515" y2="37.3244" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1282+ <line x1="42.1588" x2="49.0859" y1="30.3296" y2="37.2567" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1283+ </g>
1284+ </svg>
1285 diff --git a/ayllu/assets/favicon.ico b/ayllu/assets/favicon.ico
1286new file mode 100644
1287index 0000000..613ab78
1288 Binary files /dev/null and b/ayllu/assets/favicon.ico differ
1289 diff --git a/ayllu/assets/feed.svg b/ayllu/assets/feed.svg
1290new file mode 100644
1291index 0000000..2fd8aa4
1292--- /dev/null
1293+++ b/ayllu/assets/feed.svg
1294 @@ -0,0 +1,14 @@
1295+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1296+ <g id="color">
1297+ <path fill="#F1B31C" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60Z"/>
1298+ <circle cx="24.5" cy="47.5" r="4.5" fill="#ffffff"/>
1299+ <path fill="#ffffff" fill-rule="evenodd" d="M42 52C42 39.8497 32.1503 30 20 30V37C28.2843 37 35 43.7157 35 52H42Z" clip-rule="evenodd"/>
1300+ <path fill="#ffffff" fill-rule="evenodd" d="M52 52C52 34.3269 37.6731 20 20 20V27C33.8071 27 45 38.1929 45 52H52Z" clip-rule="evenodd"/>
1301+ </g>
1302+ <g id="line">
1303+ <path fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60V60Z"/>
1304+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M29 47.5C29 49.9853 26.9853 52 24.5 52C22.0147 52 20 49.9853 20 47.5C20 45.0147 22.0147 43 24.5 43C26.9853 43 29 45.0147 29 47.5Z"/>
1305+ <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M41 52C41 40.402 31.598 31 20 31V37C28.2843 37 35 43.7157 35 52H41Z" clip-rule="evenodd"/>
1306+ <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M52 52C52 34.3269 37.6731 20 20 20V26.0012C34.1062 26.1354 45.5 37.6121 45.5 51.75C45.5 51.8334 45.4996 51.9168 45.4988 52H52Z" clip-rule="evenodd"/>
1307+ </g>
1308+ </svg>
1309 diff --git a/ayllu/assets/feed.xsl b/ayllu/assets/feed.xsl
1310new file mode 100644
1311index 0000000..ff34fab
1312--- /dev/null
1313+++ b/ayllu/assets/feed.xsl
1314 @@ -0,0 +1,56 @@
1315+ <?xml version="1.0" encoding="utf-8"?>
1316+ <xsl:stylesheet version="3.0"
1317+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
1318+ xmlns:atom="http://www.w3.org/2005/Atom"
1319+ xmlns:dc="http://purl.org/dc/elements/1.1/"
1320+ xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
1321+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
1322+ <xsl:template match="/">
1323+ <html lang="en">
1324+ <head>
1325+ <title>
1326+ <xsl:value-of select="/rss/channel/title"/>
1327+ </title>
1328+ <link rel="stylesheet" href="/static/main.min.css" />
1329+ </head>
1330+ <body>
1331+ <main class="column">
1332+ <article>
1333+ <header>
1334+ <h1>
1335+ <img class="rss-icon-feed" src="/static/feed.svg"/>
1336+ <a>
1337+ <xsl:attribute name="href"> <xsl:value-of select="/rss/channel/link"/> </xsl:attribute>
1338+ <xsl:value-of select="/rss/channel/title"/>
1339+ </a>
1340+ </h1>
1341+ <h2><xsl:value-of select="/rss/channel/description"/></h2>
1342+ </header>
1343+ <p><strong>This is an <a href="https://en.wikipedia.org/wiki/RSS">RSS feed</a>.</strong></p>
1344+ <p>Copy the link from the address bar into your feed reader to receive regular updates.</p>
1345+ </article>
1346+ <xsl:for-each select="/rss/channel/item">
1347+ <article class="card">
1348+ <header>
1349+ <h3>
1350+ <a>
1351+ <xsl:attribute name="href">
1352+ <xsl:value-of select="link"/>
1353+ </xsl:attribute>
1354+ <xsl:value-of select="title"/>
1355+ </a>
1356+ </h3>
1357+ </header>
1358+ <xsl:value-of select="description" disable-output-escaping="yes"/>
1359+ <footer class="rss">
1360+ <xsl:value-of select="author" /><br/>
1361+ <b><xsl:value-of select="pubDate"/></b>
1362+ </footer>
1363+ </article>
1364+ </xsl:for-each>
1365+ </main>
1366+ </body>
1367+ </html>
1368+ </xsl:template>
1369+ </xsl:stylesheet>
1370+
1371 diff --git a/ayllu/assets/logo.svg b/ayllu/assets/logo.svg
1372new file mode 100644
1373index 0000000..d1db83a
1374--- /dev/null
1375+++ b/ayllu/assets/logo.svg
1376 @@ -0,0 +1,101 @@
1377+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1378+ <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" version="1.1" viewBox="0 0 902 702">
1379+ <metadata>
1380+ <rdf:RDF>
1381+ <cc:Work rdf:about="">
1382+ <dc:format>image/svg+xml</dc:format>
1383+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
1384+ </cc:Work>
1385+ </rdf:RDF>
1386+ </metadata>
1387+ <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
1388+ <g fill-rule="evenodd" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-opacity="1">
1389+ <g id="brown" fill="#5c451c" stroke="#5c451c">
1390+ <path style="" d="M 1,1 H 901 V 701 H 1 Z m 0,0"/>
1391+ </g>
1392+ <g id="orange" fill="#ffa300" stroke="#ffa300">
1393+ <path d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1394+ <path d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1395+ <path d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1396+ <path d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1397+ </g>
1398+ <g id="turquoise" fill="#358794" stroke="#358794">
1399+ <path d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1400+ <path d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1401+ <path d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1402+ <path d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1403+ <path d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1404+ <path d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1405+ <path d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1406+ <path d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1407+ <path d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1408+ <path d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1409+ <path d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1410+ <path d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1411+ </g>
1412+ <g id="white" fill="#ffffff" stroke="#ffffff">
1413+ <path d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1414+ <path d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1415+ <path d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1416+ <path d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1417+ <path d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1418+ <path d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1419+ <path d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1420+ <path d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1421+ <path d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1422+ <path d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1423+ <path d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1424+ <path d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1425+ <path d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0"/>
1426+ <path d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0"/>
1427+ <path d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0"/>
1428+ <path d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0"/>
1429+ <path d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0"/>
1430+ <path d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0"/>
1431+ <path d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0"/>
1432+ <path d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0"/>
1433+ <path d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1434+ <path d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1435+ <path d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1436+ <path d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1437+ <path d="M 1,21 H 881 V 61 H 1 Z m 0,0"/>
1438+ <path d="m 1,641 h 880 v 40 H 1 Z m 0,0"/>
1439+ <path d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1440+ <path d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1441+ <path d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1442+ <path d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
1443+ <path d="m 401,301 h 20 v 20 h -20 z m 0,0"/>
1444+ <path d="m 381,321 h 20 v 20 h -20 z m 0,0"/>
1445+ <path d="m 361,341 h 20 v 20 h -20 z m 0,0"/>
1446+ <path d="m 381,361 h 20 v 20 h -20 z m 0,0"/>
1447+ <path d="m 401,381 h 20 v 20 h -20 z m 0,0"/>
1448+ <path d="m 501,321 h 20 v 20 h -20 z m 0,0"/>
1449+ <path d="m 521,341 h 20 v 20 h -20 z m 0,0"/>
1450+ <path d="m 501,361 h 20 v 20 h -20 z m 0,0"/>
1451+ <path d="m 481,381 h 20 v 20 h -20 z m 0,0"/>
1452+ <path d="m 421,341 h 20 v 20 h -20 z m 0,0"/>
1453+ <path d="m 461,341 h 20 v 20 h -20 z m 0,0"/>
1454+ <path d="m 881,21 h 20 v 40 h -20 z m 0,0"/>
1455+ <path d="m 881,641 h 20 v 40 h -20 z m 0,0"/>
1456+ </g>
1457+ <g id="red" fill="#de3333" stroke="#de3333">
1458+ <path d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
1459+ <path d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
1460+ <path d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
1461+ <path d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
1462+ <path d="m 381,341 h 20 v 20 h -20 z m 0,0"/>
1463+ <path d="m 401,321 h 20 v 20 h -20 z m 0,0"/>
1464+ <path d="m 421,301 h 20 v 20 h -20 z m 0,0"/>
1465+ <path d="m 461,301 h 20 v 20 h -20 z m 0,0"/>
1466+ <path d="m 481,321 h 20 v 20 h -20 z m 0,0"/>
1467+ <path d="m 501,341 h 20 v 20 h -20 z m 0,0"/>
1468+ <path d="m 481,361 h 20 v 20 h -20 z m 0,0"/>
1469+ <path d="m 461,381 h 20 v 20 h -20 z m 0,0"/>
1470+ <path d="m 441,361 h 20 v 20 h -20 z m 0,0"/>
1471+ <path d="m 421,381 h 20 v 20 h -20 z m 0,0"/>
1472+ <path d="m 401,361 h 20 v 20 h -20 z m 0,0"/>
1473+ <path d="m 881,81 h 20 v 220 h -20 z m 0,0"/>
1474+ <path d="m 881,401 h 20 v 220 h -20 z m 0,0"/>
1475+ </g>
1476+ </g>
1477+ </svg>
1478 diff --git a/ayllu/assets/question.svg b/ayllu/assets/question.svg
1479new file mode 100644
1480index 0000000..9ee75dd
1481--- /dev/null
1482+++ b/ayllu/assets/question.svg
1483 @@ -0,0 +1,13 @@
1484+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1485+ <g id="color">
1486+ <path fill="#fff" stroke-miterlimit="10" d="m48.72 20.15c0.3051 9.298-8.021 12.75-10.82 19.21v7.565c0 1.39-1.11 2.5-2.5 2.5-1.38 0-2.5-1.11-2.5-2.5v-9.865c3.493-6.142 10.38-7.469 10.67-17.06 0-5.66-6.543-6.151-7.988-6.175h-0.05c-5.767 1.187-6.135 4.99-7.289 9.549-0.6671 1.521-1.564 2.045-2.9 2.03-1.36-0.2401-2.26-1.54-2.02-2.9 0.5877-2.752 0.758-6.639 2.35-8.502 2.71-3.14 4.819-4.957 9.179-5.167 0.24 0 0.49-0.01 0.73-0.01 0.09 0 0.18 0 0.27 0.01 8.27 0.2401 12.6 2.992 12.87 11.32z"/>
1487+ <circle cx="34.95" cy="58.84" r="3" fill="#fff" stroke-miterlimit="10"/>
1488+ </g>
1489+ <g id="hair"/>
1490+ <g id="skin"/>
1491+ <g id="skin-shadow"/>
1492+ <g id="line">
1493+ <circle cx="35.3" cy="58.84" r="2.625" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.75"/>
1494+ <path d="m35.67 10.07c6.57 0 12.03 3.431 12.03 10.21 0 3.671-0.48 6.526-3.71 9.755-3.956 3.956-7.184 6.346-7.208 8.549-0.0651 5.826-5e-3 8.278-0.0044 8.36a1.476 1.476 0 0 1-1.464 1.487h-0.0115a1.476 1.476 0 0 1-1.476-1.464c-5e-4 -0.0819-0.0607-2.561 0.0044-8.416 0.0358-3.231 3.63-6.127 8.385-10.88 2.178-2.174 2.474-4.716 2.474-7.127 0-3.282-2.965-7.587-8.984-7.587-5.338 0-8.499 3.467-8.499 9.552a1.434 1.434 0 0 1-1.395 1.587 1.552 1.552 0 0 1-1.505-1.759c0-9.882 7.291-12.27 11.36-12.27m0-2c-4.964 0-13.36 3.005-13.36 14.27a3.519 3.519 0 0 0 3.505 3.759 3.418 3.418 0 0 0 3.395-3.587c0-3.445 1.128-7.552 6.499-7.552 4.817 0 6.984 3.267 6.984 5.587 0 2.364-0.3155 4.144-1.886 5.712-0.7515 0.75-1.474 1.454-2.157 2.12-3.933 3.833-6.775 6.602-6.815 10.15-0.055 4.946-0.0218 7.539-5e-3 8.424a3.476 3.476 0 1 0 6.952-0.0248l-0.0012-0.075c-0.0164-0.8595-0.0488-3.384 0.0056-8.248 0.0092-0.8384 2.102-2.822 3.948-4.572 0.8091-0.767 1.726-1.636 2.675-2.585 3.577-3.577 4.296-6.926 4.296-11.17 0-7.301-5.638-12.21-14.03-12.21z"/>
1495+ </g>
1496+ </svg>
1497 diff --git a/ayllu/assets/rss.svg b/ayllu/assets/rss.svg
1498new file mode 100644
1499index 0000000..b325149
1500--- /dev/null
1501+++ b/ayllu/assets/rss.svg
1502 @@ -0,0 +1,18 @@
1503+ <?xml version="1.0"?>
1504+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
1505+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256">
1506+ <defs>
1507+ <linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
1508+ <stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/>
1509+ <stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/>
1510+ <stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/>
1511+ <stop offset="1.0" stop-color="#D95B29"/>
1512+ </linearGradient>
1513+ </defs>
1514+ <rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/>
1515+ <rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/>
1516+ <rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/>
1517+ <circle cx="68" cy="189" r="24" fill="#FFF"/>
1518+ <path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
1519+ <path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
1520+ </svg>
1521 diff --git a/ayllu/assets/scale.svg b/ayllu/assets/scale.svg
1522new file mode 100644
1523index 0000000..8ffe8c3
1524--- /dev/null
1525+++ b/ayllu/assets/scale.svg
1526 @@ -0,0 +1,22 @@
1527+ <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
1528+ <g id="color">
1529+ <path fill="#9B9B9A" stroke="none" d="M48.0626,62.92c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6H48.0626z"/>
1530+ <path fill="#D0CFCE" stroke="none" d="M24.9006,46.9656c0,2.1938-2.1985,3.9723-4.9106,3.9723s-4.9106-1.7785-4.9106-3.9723H24.9006z"/>
1531+ <path fill="#D0CFCE" stroke="none" d="M57.9006,46.9248c0,2.1938-2.1985,3.9723-4.9106,3.9723c-2.712,0-4.9106-1.7785-4.9106-3.9723H57.9006z"/>
1532+ </g>
1533+ <g id="hair"/>
1534+ <g id="skin"/>
1535+ <g id="skin-shadow"/>
1536+ <g id="line">
1537+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.9996,25.1697c2.6667,0.0001,8.9544-5.3333,20.0002-5.3333S50.6666,23.8365,56,25.1697"/>
1538+ <line x1="35.9998" x2="35.9998" y1="23.9064" y2="51.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1539+ <circle cx="35.9998" cy="13.895" r="3" fill="#000000" stroke="none"/>
1540+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48,61.9064c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6"/>
1541+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M26,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H26z"/>
1542+ <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="20,27.9064 15,45.9064 25,45.9064"/>
1543+ <line x1="20" x2="20" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1544+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H59z"/>
1545+ <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="53,27.9064 48,45.9064 58,45.9064"/>
1546+ <line x1="53" x2="53" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
1547+ </g>
1548+ </svg>
1549 diff --git a/ayllu/assets/textile-pattern-1.svg b/ayllu/assets/textile-pattern-1.svg
1550new file mode 100644
1551index 0000000..28124d3
1552--- /dev/null
1553+++ b/ayllu/assets/textile-pattern-1.svg
1554 @@ -0,0 +1,90 @@
1555+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1556+ <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
1557+ <metadata id="metadata165">
1558+ <rdf:RDF>
1559+ <cc:Work rdf:about="">
1560+ <dc:format>image/svg+xml</dc:format>
1561+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
1562+ </cc:Work>
1563+ </rdf:RDF>
1564+ </metadata>
1565+ <defs id="defs163"/>
1566+ <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
1567+ <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
1568+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
1569+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
1570+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
1571+ <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
1572+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
1573+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
1574+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
1575+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
1576+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
1577+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
1578+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
1579+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
1580+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
1581+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
1582+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
1583+ <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
1584+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
1585+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
1586+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
1587+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
1588+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
1589+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
1590+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
1591+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
1592+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
1593+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
1594+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
1595+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
1596+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
1597+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
1598+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
1599+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
1600+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
1601+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
1602+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
1603+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
1604+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
1605+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
1606+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
1607+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
1608+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
1609+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
1610+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
1611+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
1612+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
1613+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
1614+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
1615+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
1616+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
1617+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
1618+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
1619+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
1620+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
1621+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
1622+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
1623+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
1624+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
1625+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
1626+ <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
1627+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
1628+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
1629+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
1630+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
1631+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
1632+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
1633+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
1634+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
1635+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
1636+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
1637+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
1638+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
1639+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
1640+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
1641+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
1642+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
1643+ <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
1644+ </svg>
1645 diff --git a/ayllu/assets/textile-pattern-2.svg b/ayllu/assets/textile-pattern-2.svg
1646new file mode 100644
1647index 0000000..f442f7a
1648--- /dev/null
1649+++ b/ayllu/assets/textile-pattern-2.svg
1650 @@ -0,0 +1,308 @@
1651+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1652+ <svg
1653+ xmlns:dc="http://purl.org/dc/elements/1.1/"
1654+ xmlns:cc="http://creativecommons.org/ns#"
1655+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
1656+ xmlns:svg="http://www.w3.org/2000/svg"
1657+ xmlns="http://www.w3.org/2000/svg"
1658+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
1659+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
1660+ width="1441.0001pt"
1661+ height="1441pt"
1662+ viewBox="0 0 1441.0001 1441"
1663+ version="1.1"
1664+ id="svg11"
1665+ sodipodi:docname="Manto-funerario-paracas-2.svg"
1666+ inkscape:version="1.0 (1.0+r73+1)">
1667+ <metadata
1668+ id="metadata17">
1669+ <rdf:RDF>
1670+ <cc:Work
1671+ rdf:about="">
1672+ <dc:format>image/svg+xml</dc:format>
1673+ <dc:type
1674+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
1675+ <dc:title />
1676+ </cc:Work>
1677+ </rdf:RDF>
1678+ </metadata>
1679+ <defs
1680+ id="defs15" />
1681+ <sodipodi:namedview
1682+ inkscape:document-rotation="0"
1683+ pagecolor="#ffffff"
1684+ bordercolor="#666666"
1685+ borderopacity="1"
1686+ objecttolerance="10"
1687+ gridtolerance="10"
1688+ guidetolerance="10"
1689+ inkscape:pageopacity="0"
1690+ inkscape:pageshadow="2"
1691+ inkscape:window-width="1853"
1692+ inkscape:window-height="1025"
1693+ id="namedview13"
1694+ showgrid="false"
1695+ inkscape:zoom="0.18438366"
1696+ inkscape:cx="471.3773"
1697+ inkscape:cy="745.57387"
1698+ inkscape:window-x="67"
1699+ inkscape:window-y="27"
1700+ inkscape:window-maximized="1"
1701+ inkscape:current-layer="svg11" />
1702+ <g
1703+ transform="translate(-0.49999149,719.5)"
1704+ id="g921">
1705+ <path
1706+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1707+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
1708+ id="path4" />
1709+ <path
1710+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1711+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
1712+ id="path6" />
1713+ <path
1714+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1715+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
1716+ id="path8" />
1717+ </g>
1718+ <g
1719+ id="g895"
1720+ transform="translate(384.03529,784.594)">
1721+ <path
1722+ id="path4-2"
1723+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1724+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1725+ <path
1726+ id="path6-9"
1727+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1728+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1729+ <path
1730+ id="path8-1"
1731+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1732+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1733+ </g>
1734+ <g
1735+ id="g944"
1736+ transform="translate(-198.02416,1142.587)">
1737+ <path
1738+ id="path4-27"
1739+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
1740+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1741+ <path
1742+ id="path6-0"
1743+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
1744+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1745+ <path
1746+ id="path8-9"
1747+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
1748+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1749+ </g>
1750+ <g
1751+ transform="translate(744.03529,424.59399)"
1752+ id="g895-3">
1753+ <path
1754+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1755+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1756+ id="path4-2-6" />
1757+ <path
1758+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1759+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1760+ id="path6-9-0" />
1761+ <path
1762+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1763+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1764+ id="path8-1-6" />
1765+ </g>
1766+ <g
1767+ id="g921-2"
1768+ transform="translate(359.50001,359.5)">
1769+ <path
1770+ id="path4-6"
1771+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
1772+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1773+ <path
1774+ id="path6-1"
1775+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
1776+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1777+ <path
1778+ id="path8-8"
1779+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
1780+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1781+ </g>
1782+ <g
1783+ id="g921-9"
1784+ transform="translate(719.50001,-0.5)">
1785+ <path
1786+ id="path4-7"
1787+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
1788+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1789+ <path
1790+ id="path6-3"
1791+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
1792+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1793+ <path
1794+ id="path8-6"
1795+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
1796+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1797+ </g>
1798+ <g
1799+ transform="translate(1104.0353,64.594)"
1800+ id="g895-1">
1801+ <path
1802+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1803+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1804+ id="path4-2-2" />
1805+ <path
1806+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1807+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1808+ id="path6-9-9" />
1809+ <path
1810+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1811+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1812+ id="path8-1-3" />
1813+ </g>
1814+ <g
1815+ transform="translate(521.97583,422.58699)"
1816+ id="g944-1">
1817+ <path
1818+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1819+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
1820+ id="path4-27-9" />
1821+ <path
1822+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1823+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
1824+ id="path6-0-4" />
1825+ <path
1826+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1827+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
1828+ id="path8-9-7" />
1829+ </g>
1830+ <g
1831+ id="g895-3-8"
1832+ transform="translate(1464.0353,-295.406)">
1833+ <path
1834+ id="path4-2-6-4"
1835+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1836+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1837+ <path
1838+ id="path6-9-0-5"
1839+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1840+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1841+ <path
1842+ id="path8-1-6-0"
1843+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1844+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1845+ </g>
1846+ <g
1847+ transform="translate(161.97582,782.587)"
1848+ id="g944-3">
1849+ <path
1850+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1851+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
1852+ id="path4-27-6" />
1853+ <path
1854+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1855+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
1856+ id="path6-0-1" />
1857+ <path
1858+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1859+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
1860+ id="path8-9-0" />
1861+ </g>
1862+ <g
1863+ id="g921-6"
1864+ transform="translate(719.50003,1079.5)">
1865+ <path
1866+ id="path4-3"
1867+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
1868+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1869+ <path
1870+ id="path6-2"
1871+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
1872+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1873+ <path
1874+ id="path8-0"
1875+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
1876+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1877+ </g>
1878+ <g
1879+ transform="translate(1079.5,719.5)"
1880+ id="g921-2-6">
1881+ <path
1882+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1883+ d="M 1,1 H 361 V 361 H 1 Z m 0,0"
1884+ id="path4-6-1" />
1885+ <path
1886+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1887+ d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
1888+ id="path6-1-5" />
1889+ <path
1890+ style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1891+ d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
1892+ id="path8-8-5" />
1893+ </g>
1894+ <g
1895+ transform="translate(-558.02418,422.58698)"
1896+ id="g944-4">
1897+ <path
1898+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
1899+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
1900+ id="path4-27-7" />
1901+ <path
1902+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1903+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
1904+ id="path6-0-6" />
1905+ <path
1906+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
1907+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
1908+ id="path8-9-5" />
1909+ </g>
1910+ <g
1911+ id="g944-3-6"
1912+ transform="translate(-198.02419,62.58699)">
1913+ <path
1914+ id="path4-27-6-9"
1915+ d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
1916+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1917+ <path
1918+ id="path6-0-1-3"
1919+ d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
1920+ style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1921+ <path
1922+ id="path8-9-0-7"
1923+ d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
1924+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1925+ </g>
1926+ <g
1927+ id="g895-1-4"
1928+ transform="translate(384.03528,-295.406)">
1929+ <path
1930+ id="path4-2-2-5"
1931+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1932+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1933+ <path
1934+ id="path6-9-9-2"
1935+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1936+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1937+ <path
1938+ id="path8-1-3-5"
1939+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1940+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1941+ </g>
1942+ <g
1943+ id="g895-1-47"
1944+ transform="translate(1464.0353,784.594)">
1945+ <path
1946+ id="path4-2-2-4"
1947+ d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
1948+ style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
1949+ <path
1950+ id="path6-9-9-4"
1951+ d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
1952+ style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1953+ <path
1954+ id="path8-1-3-3"
1955+ d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
1956+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
1957+ </g>
1958+ </svg>
1959 diff --git a/ayllu/assets/xmpp.svg b/ayllu/assets/xmpp.svg
1960new file mode 100644
1961index 0000000..0c904a7
1962--- /dev/null
1963+++ b/ayllu/assets/xmpp.svg
1964 @@ -0,0 +1,30 @@
1965+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1966+ <!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
1967+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
1968+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xml:space="preserve" viewBox="0 0 200 200" width="200px" height="200px" x="0px" y="0px" enable-background="new 0 0 200 200">
1969+
1970+ <linearGradient id="SVGID_right_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="translate(1196.604,15.368977)" y1="126.85" x1="-1073.2">
1971+ <stop stop-color="#1b3967" offset=".011"/>
1972+ <stop stop-color="#13b5ea" offset=".467"/>
1973+ <stop stop-color="#002b5c" offset=".9945"/>
1974+ </linearGradient>
1975+
1976+ <linearGradient id="SVGID_left_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)" y1="126.85" x1="-1073.2">
1977+ <stop stop-color="#1b3967" offset=".011"/>
1978+ <stop stop-color="#13b5ea" offset=".467"/>
1979+ <stop stop-color="#002b5c" offset=".9945"/>
1980+ </linearGradient>
1981+
1982+ <path d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z" style="fill:url(#SVGID_right_)"/>
1983+ <path d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z" style="fill:#e96d1f"/>
1984+ <path d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z" style="fill:#d9541e"/>
1985+
1986+ <path d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z" style="fill:url(#SVGID_left_)"/>
1987+ <path d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z" style="fill:#a0ce67"/>
1988+ <path d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z" style="fill:#439639"/>
1989+
1990+ <path d="m 25.988,172.07799 -13.388,-14.65 h 11.643 l 9.127,10.268 9.129,-10.268 h 11.643 l -13.387,14.646 14.401,14.728 h -12.09 l -9.697,-10.67 -9.693,10.67 H 11.584 l 14.404,-14.73 z"/>
1991+ <path d="m 58.508,157.42799 h 13.836 l 10.183,18.905 10.183,-18.905 h 13.83199 v 29.374 h -8.761983 v -21.096 h -0.08 L 85.893,186.80199 H 79.16 l -11.807,-21.096 h -0.082 v 21.096 h -8.764 v -29.37 z"/>
1992+ <path d="m 112.66199,157.42799 h 24.546 c 8.559,0 10.628,4.302 10.628,10.063 v 2.516 c 0,4.381 -1.908,9.41 -8.275,9.41 h -17.894 v 7.385 h -9.005 v -29.38 z m 9,14.69 h 13.997 c 2.10901,0 2.92401,-1.377 2.92401,-3.123 v -1.135 c 0,-1.99 -0.976,-3.127 -3.694,-3.127 h -13.227 v 7.38 z"/>
1993+ <path d="m 152.72199,157.42799 h 24.546 c 8.561,0 10.63,4.302 10.63,10.063 v 2.516 c 0,4.381 -1.907,9.41 -8.275,9.41 h -17.893 v 7.385 h -9.008 v -29.38 z m 9.01,14.69 h 13.996 c 2.11,0 2.922,-1.377 2.922,-3.123 v -1.135 c 0,-1.99 -0.974,-3.127 -3.693,-3.127 h -13.225 v 7.38 z"/>
1994+ </svg>
1995\ No newline at end of file
1996 diff --git a/ayllu/build.rs b/ayllu/build.rs
1997deleted file mode 100644
1998index abafb99..0000000
1999--- a/ayllu/build.rs
2000+++ /dev/null
2001 @@ -1,30 +0,0 @@
2002- use lightningcss::{
2003- bundler::{Bundler, FileProvider},
2004- stylesheet::{ParserOptions, PrinterOptions},
2005- };
2006-
2007- use std::env::var;
2008- use std::fs::{create_dir_all, File};
2009- use std::io::prelude::*;
2010- use std::path::Path;
2011-
2012- fn main() {
2013- let fs = FileProvider::new();
2014- let mut bundler = Bundler::new(&fs, None, ParserOptions::default());
2015- // compiles a minified version of the base theme which all additional
2016- // themes inherit from.
2017- let out_dir = var("OUT_DIR").unwrap();
2018- let stylesheet = bundler
2019- .bundle(Path::new("themes/default/theme.css"))
2020- .unwrap();
2021- let css = stylesheet
2022- .to_css(PrinterOptions {
2023- minify: true,
2024- ..Default::default()
2025- })
2026- .unwrap();
2027- let theme_dir = Path::new(&out_dir).join("themes/default");
2028- create_dir_all(theme_dir.clone()).unwrap();
2029- let mut file = File::create(theme_dir.join("theme.min.css")).unwrap();
2030- write!(file, "{}", css.code).unwrap();
2031- }
2032 diff --git a/ayllu/src/config.rs b/ayllu/src/config.rs
2033index dd61613..e35b1aa 100644
2034--- a/ayllu/src/config.rs
2035+++ b/ayllu/src/config.rs
2036 @@ -2,7 +2,6 @@ use std::collections::HashMap;
2037 use std::error::Error;
2038 use std::fs::metadata;
2039 use std::num::NonZeroUsize;
2040- use std::path::PathBuf;
2041 use std::thread::available_parallelism;
2042 use url::Url;
2043
2044 @@ -95,7 +94,7 @@ pub struct List {
2045 #[derive(Deserialize, Serialize, Clone, Debug)]
2046 pub struct Theme {
2047 pub name: String,
2048- pub path: PathBuf,
2049+ pub css_content: String,
2050 }
2051
2052 #[derive(Deserialize, Serialize, Clone, Debug)]
2053 @@ -103,7 +102,8 @@ pub struct Web {
2054 pub themes_path: Option<String>,
2055 #[serde(default = "Web::default_default_theme")]
2056 pub default_theme: String,
2057- pub themes: Option<Vec<Theme>>,
2058+ #[serde(default = "Web::default_themes")]
2059+ pub themes: Vec<Theme>,
2060 #[serde(default = "Web::default_unsafe_markdown")]
2061 pub unsafe_markdown: bool,
2062 }
2063 @@ -113,15 +113,32 @@ impl Default for Web {
2064 Self {
2065 themes_path: None,
2066 default_theme: Web::default_default_theme(),
2067- themes: None,
2068+ themes: Web::default_themes(),
2069 unsafe_markdown: Web::default_unsafe_markdown(),
2070 }
2071 }
2072 }
2073
2074 impl Web {
2075+ fn default_themes() -> Vec<Theme> {
2076+ vec![
2077+ Theme {
2078+ name: String::from("default"),
2079+ css_content: include_str!("../themes/default.css").to_string(),
2080+ },
2081+ Theme {
2082+ name: String::from("nord"),
2083+ css_content: include_str!("../themes/nord.css").to_string(),
2084+ },
2085+ Theme {
2086+ name: String::from("catppuccin"),
2087+ css_content: include_str!("../themes/catppuccin.css").to_string(),
2088+ },
2089+ ]
2090+ }
2091+
2092 fn default_default_theme() -> String {
2093- String::from("default")
2094+ String::from("nord")
2095 }
2096
2097 fn default_unsafe_markdown() -> bool {
2098 diff --git a/ayllu/src/highlight.rs b/ayllu/src/highlight.rs
2099index 50ca239..caf228f 100644
2100--- a/ayllu/src/highlight.rs
2101+++ b/ayllu/src/highlight.rs
2102 @@ -9,7 +9,6 @@ use std::sync::RwLock;
2103 use comrak::adapters::SyntaxHighlighterAdapter;
2104 use lazy_static::lazy_static;
2105 use log::debug;
2106- use tera::escape_html;
2107 use tree_sitter::Language;
2108 use tree_sitter_highlight::{HighlightConfiguration, Highlighter as TSHighlighter, HtmlRenderer};
2109
2110 @@ -205,6 +204,7 @@ fn render_lines(lines: Vec<&str>, show_line_numbers: bool) -> String {
2111 // write!(&mut file, "<table class=\"code\">").unwrap();
2112 let max_chars = lines.into_iter().enumerate().fold(0, |accm, (i, line)| {
2113 let n_chars = line.chars().count();
2114+ // let escaped = askama::filters::escape(line, askama::filters::Html).unwrap();
2115 if show_line_numbers {
2116 write!(
2117 &mut file,
2118 @@ -325,7 +325,7 @@ impl Highlighter {
2119 debug!("cannot paint syntax for language: {:?}", hint);
2120 (
2121 None,
2122- render_lines(escape_html(code).lines().collect(), show_line_numbers),
2123+ render_lines(code.lines().collect(), show_line_numbers),
2124 )
2125 }
2126 },
2127 @@ -333,7 +333,7 @@ impl Highlighter {
2128 debug!("cannot determine language");
2129 (
2130 None,
2131- render_lines(escape_html(code).lines().collect(), show_line_numbers),
2132+ render_lines(code.lines().collect(), show_line_numbers),
2133 )
2134 }
2135 }
2136 diff --git a/ayllu/src/languages.rs b/ayllu/src/languages.rs
2137index c89e05c..9c386b0 100644
2138--- a/ayllu/src/languages.rs
2139+++ b/ayllu/src/languages.rs
2140 @@ -1,5 +1,6 @@
2141 use std::cmp::{Eq, PartialEq};
2142 use std::collections::HashMap;
2143+ use std::fmt::Display;
2144 use std::hash::Hash;
2145 use std::io::Read;
2146 use std::path::Path;
2147 @@ -47,6 +48,12 @@ impl From<&str> for Hint {
2148 }
2149 }
2150
2151+ impl Display for Hint {
2152+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2153+ write!(f, "{}", self.0)
2154+ }
2155+ }
2156+
2157 #[derive(Deserialize, Clone, Debug)]
2158 struct Language {
2159 #[serde(default = "Language::black")]
2160 diff --git a/ayllu/src/main.rs b/ayllu/src/main.rs
2161index 6b02edd..ada90a4 100644
2162--- a/ayllu/src/main.rs
2163+++ b/ayllu/src/main.rs
2164 @@ -15,7 +15,6 @@ mod highlight;
2165 mod languages;
2166 mod license;
2167 mod readme;
2168- mod time;
2169
2170 #[derive(Parser, Debug)]
2171 #[clap(version, about, long_about = "hyper performant code forge")]
2172 diff --git a/ayllu/src/time.rs b/ayllu/src/time.rs
2173deleted file mode 100644
2174index d477ccd..0000000
2175--- a/ayllu/src/time.rs
2176+++ /dev/null
2177 @@ -1,54 +0,0 @@
2178- use std::time::SystemTime;
2179-
2180- // apparently these exist in nightly rust?
2181- const SECOND: u64 = 1;
2182- const MINUTE: u64 = SECOND * 60;
2183- const HOUR: u64 = MINUTE * 60;
2184- const DAY: u64 = HOUR * 24;
2185- const WEEK: u64 = DAY * 7;
2186- const MONTH: u64 = DAY * 30;
2187- const YEAR: u64 = DAY * 365;
2188-
2189- fn pluralize(value: u64) -> String {
2190- match value {
2191- 1 => String::new(),
2192- _ => String::from("s"),
2193- }
2194- }
2195-
2196- // convert number of a seconds into a friendly string, e.g.
2197- // recently, 1s ago, 1m ago, 1h ago, 1d ago, 1mo ago, 1y ago
2198- // FIXME: why is this u64
2199- pub fn friendly(seconds: u64) -> String {
2200- let now = SystemTime::now()
2201- .duration_since(SystemTime::UNIX_EPOCH)
2202- .unwrap();
2203- let seconds = now.as_secs() - seconds;
2204- let message: String;
2205- if seconds == 0 {
2206- message = "recently".to_string();
2207- } else if seconds < MINUTE {
2208- message = format!("{} second{} ago", seconds, pluralize(seconds));
2209- } else if seconds < HOUR {
2210- message = format!(
2211- "{} minute{} ago",
2212- seconds / MINUTE,
2213- pluralize(seconds / MINUTE),
2214- )
2215- } else if seconds < DAY {
2216- message = format!("{} hour{} ago", seconds / HOUR, pluralize(seconds / HOUR),)
2217- } else if seconds < WEEK {
2218- message = format!("{} day{} ago", seconds / DAY, pluralize(seconds / DAY),)
2219- } else if seconds < MONTH {
2220- message = format!("{} week{} ago", seconds / WEEK, pluralize(seconds / WEEK),)
2221- } else if seconds < YEAR {
2222- message = format!(
2223- "{} month{} ago",
2224- seconds / MONTH,
2225- pluralize(seconds / MONTH),
2226- )
2227- } else {
2228- message = format!("{} year{} ago", seconds / YEAR, pluralize(seconds / YEAR),)
2229- }
2230- message
2231- }
2232 diff --git a/ayllu/src/web2/error.rs b/ayllu/src/web2/error.rs
2233index f0fce2c..aebbd59 100644
2234--- a/ayllu/src/web2/error.rs
2235+++ b/ayllu/src/web2/error.rs
2236 @@ -5,7 +5,6 @@ use axum::{
2237 response::{IntoResponse, Response},
2238 };
2239 use serde::Serialize;
2240- use tera::Error as TeraError;
2241
2242 use ayllu_api::error::ApiError;
2243 use ayllu_git::Error as GitError;
2244 @@ -38,12 +37,6 @@ impl From<ApiError> for Error {
2245 }
2246 }
2247
2248- impl From<TeraError> for Error {
2249- fn from(value: TeraError) -> Self {
2250- Error::Message(format!("Templating: {:?}", value))
2251- }
2252- }
2253-
2254 impl From<GitError> for Error {
2255 fn from(value: GitError) -> Self {
2256 if value.not_found() {
2257 @@ -62,3 +55,9 @@ impl From<IoError> for Error {
2258 }
2259 }
2260 }
2261+
2262+ impl From<askama::Error> for Error {
2263+ fn from(value: askama::Error) -> Self {
2264+ Error::Message(format!("Template Error: {:?}", value))
2265+ }
2266+ }
2267 diff --git a/ayllu/src/web2/middleware/error.rs b/ayllu/src/web2/middleware/error.rs
2268index 03bbdf3..a8e1424 100644
2269--- a/ayllu/src/web2/middleware/error.rs
2270+++ b/ayllu/src/web2/middleware/error.rs
2271 @@ -1,5 +1,6 @@
2272 use std::sync::Arc;
2273
2274+ use askama::Template;
2275 use axum::{
2276 extract,
2277 http::StatusCode,
2278 @@ -7,16 +8,31 @@ use axum::{
2279 response::{Html, IntoResponse, Response},
2280 };
2281
2282- use crate::config::Config;
2283 use crate::web2::error::Error;
2284 use crate::web2::extractors::config::ConfigReader;
2285- use crate::web2::terautil::{Loader, Options, Themes};
2286+ use crate::{config::Config, web2::template::Base};
2287
2288- pub type State = Arc<(Config, Themes)>;
2289+ pub type State = Arc<Config>;
2290+
2291+ #[derive(Template)]
2292+ #[template(path = "404.html")]
2293+ struct NotFoundPage<'a> {
2294+ base: Base,
2295+ status_code: &'a str,
2296+ error_message: &'a str,
2297+ }
2298+
2299+ #[derive(Template)]
2300+ #[template(path = "5xx.html")]
2301+ struct ErrorPage<'a> {
2302+ base: Base,
2303+ status_code: &'a str,
2304+ error_message: &'a str,
2305+ }
2306
2307 pub async fn middleware(
2308- extract::State(state): extract::State<State>,
2309- ConfigReader(client_config): ConfigReader,
2310+ extract::State(_state): extract::State<State>,
2311+ ConfigReader(_client_config): ConfigReader,
2312 req: extract::Request,
2313 next: Next,
2314 ) -> Response {
2315 @@ -27,29 +43,37 @@ pub async fn middleware(
2316 Error::NotFound(_) => StatusCode::NOT_FOUND,
2317 Error::ComponentNotEnabled(_) => StatusCode::INTERNAL_SERVER_ERROR,
2318 };
2319- // reload the theme since the middleware may not have ran yet
2320- let loader = Loader {
2321- themes: state.1.clone(),
2322- default_theme: state.0.web.default_theme.clone(),
2323- };
2324- let (template, mut ctx) = loader.load(
2325- Options {
2326- origin: state.0.origin.clone(),
2327- site_name: state.0.site_name.clone(),
2328- ..Default::default()
2329- },
2330- client_config.theme,
2331- );
2332- ctx.insert("status_code", status_code.as_str());
2333- ctx.insert("error_message", &error.to_string());
2334- let template_str = if status_code == StatusCode::NOT_FOUND {
2335+ if status_code == StatusCode::NOT_FOUND {
2336 log::warn!("Not Found: {}", error.to_string());
2337- template.render("404.html", &ctx).unwrap()
2338+ return Html::from(
2339+ NotFoundPage {
2340+ base: Base {
2341+ title: String::from("Resource Not Found"),
2342+ ..Default::default()
2343+ },
2344+ status_code: "404",
2345+ error_message: &error.to_string(),
2346+ }
2347+ .render()
2348+ .unwrap(),
2349+ )
2350+ .into_response();
2351 } else {
2352 log::error!("Error: {}", error.to_string());
2353- template.render("5xx.html", &ctx).unwrap()
2354+ return Html::from(
2355+ ErrorPage {
2356+ base: Base {
2357+ title: String::from("Internal Server Error"),
2358+ ..Default::default()
2359+ },
2360+ status_code: status_code.as_str(),
2361+ error_message: &error.to_string(),
2362+ }
2363+ .render()
2364+ .unwrap(),
2365+ )
2366+ .into_response();
2367 };
2368- return Html::from(template_str).into_response();
2369 }
2370 response
2371 }
2372 diff --git a/ayllu/src/web2/middleware/mod.rs b/ayllu/src/web2/middleware/mod.rs
2373index 15f8cf8..3f2a883 100644
2374--- a/ayllu/src/web2/middleware/mod.rs
2375+++ b/ayllu/src/web2/middleware/mod.rs
2376 @@ -2,4 +2,3 @@ pub mod error;
2377 pub mod repository;
2378 pub mod sites;
2379 pub mod template;
2380- pub mod theme;
2381 diff --git a/ayllu/src/web2/middleware/template.rs b/ayllu/src/web2/middleware/template.rs
2382index 1907ed2..8f88a66 100644
2383--- a/ayllu/src/web2/middleware/template.rs
2384+++ b/ayllu/src/web2/middleware/template.rs
2385 @@ -1,48 +1,20 @@
2386- use serde::Deserialize;
2387- use std::sync::Arc;
2388- use tera::{Context as TeraContext, Tera};
2389+ use axum::{
2390+ extract::{Request, State},
2391+ middleware::Next,
2392+ response::Response,
2393+ };
2394
2395- use axum::{extract, middleware::Next, response::Response};
2396-
2397- use crate::config::Config;
2398- use crate::web2::extractors::config::ConfigReader;
2399- use crate::web2::terautil::{Loader, Options, Themes};
2400-
2401- pub type Template = (Tera, TeraContext);
2402- pub type State = extract::State<Arc<(Config, Themes)>>;
2403-
2404- #[derive(Deserialize)]
2405- pub struct CommonParams {
2406- pub collection: Option<String>,
2407- pub name: Option<String>,
2408- }
2409-
2410- /// initialize the currently configured theme as a (tera, context)
2411- pub async fn middleware(
2412- extract::State(state): State,
2413- ConfigReader(config): ConfigReader,
2414- extract::Path(CommonParams { collection, name }): extract::Path<CommonParams>,
2415- mut req: extract::Request,
2416- next: Next,
2417- ) -> Response {
2418- let loader = Loader {
2419- themes: state.1.clone(),
2420- default_theme: state.0.web.default_theme.clone(),
2421- };
2422- let (template, ctx) = loader.load(
2423- Options {
2424- origin: state.0.origin.clone(),
2425- collection,
2426- name,
2427- site_name: state.0.site_name.clone(),
2428- url: req.uri().to_string(),
2429- path: req.uri().path().to_string(),
2430- subpath_mode: state.0.subpath_mode,
2431- ..Options::default()
2432- },
2433- config.theme,
2434- );
2435- req.extensions_mut().insert((template, ctx));
2436+ use crate::{
2437+ config::Config,
2438+ web2::template::{Base, DEFAULT_LOGO, DEFAULT_RSS_ICON},
2439+ };
2440
2441+ pub async fn middleware(State(config): State<Config>, mut req: Request, next: Next) -> Response {
2442+ req.extensions_mut().insert(Base {
2443+ subpath_mode: config.subpath_mode,
2444+ logo: DEFAULT_LOGO.to_string(),
2445+ feed_icon: DEFAULT_RSS_ICON.to_string(),
2446+ ..Default::default()
2447+ });
2448 next.run(req).await
2449 }
2450 diff --git a/ayllu/src/web2/middleware/theme.rs b/ayllu/src/web2/middleware/theme.rs
2451deleted file mode 100644
2452index 1228750..0000000
2453--- a/ayllu/src/web2/middleware/theme.rs
2454+++ /dev/null
2455 @@ -1,25 +0,0 @@
2456- use std::sync::Arc;
2457-
2458- use axum::{extract, middleware::Next, response::Response};
2459-
2460- use crate::config::Config;
2461- use crate::web2::extractors::config::ConfigReader;
2462- use crate::web2::terautil::{Loader, Themes};
2463-
2464- pub type State = extract::State<Arc<(Config, Themes)>>;
2465-
2466- /// adds the current theme as an extension for direct access
2467- pub async fn middleware(
2468- extract::State(state): State,
2469- ConfigReader(config): ConfigReader,
2470- mut req: extract::Request,
2471- next: Next,
2472- ) -> Response {
2473- let loader = Loader {
2474- themes: state.1.clone(),
2475- default_theme: state.0.web.default_theme.clone(),
2476- };
2477- let theme = loader.theme(config.theme.as_deref());
2478- req.extensions_mut().insert(theme);
2479- next.run(req).await
2480- }
2481 diff --git a/ayllu/src/web2/mod.rs b/ayllu/src/web2/mod.rs
2482index e5ffca5..e9fa26c 100644
2483--- a/ayllu/src/web2/mod.rs
2484+++ b/ayllu/src/web2/mod.rs
2485 @@ -5,7 +5,7 @@ mod middleware;
2486 mod navigation;
2487 mod routes;
2488 mod server;
2489- mod terautil;
2490 mod util;
2491+ mod template;
2492
2493 pub use server::serve;
2494 diff --git a/ayllu/src/web2/navigation.rs b/ayllu/src/web2/navigation.rs
2495index f69e24b..43e19a3 100644
2496--- a/ayllu/src/web2/navigation.rs
2497+++ b/ayllu/src/web2/navigation.rs
2498 @@ -68,11 +68,6 @@ pub fn subnav(
2499 current_page == "blob",
2500 ),
2501 (
2502- String::from("blame"),
2503- format!("/{}/{}/blame/{}/{}", collection, name, ref_name, file_path),
2504- current_page == "blame",
2505- ),
2506- (
2507 String::from("log"),
2508 format!("/{}/{}/log/{}/{}", collection, name, ref_name, file_path,),
2509 current_page == "log",
2510 @@ -92,20 +87,3 @@ pub fn subnav(
2511 ),
2512 ]
2513 }
2514-
2515- #[allow(dead_code)]
2516- pub fn chartnav(current_page: &str, collection: &str, name: &str, git_hash: Option<&str>) -> Items {
2517- let commit_path = git_hash.map_or(String::new(), |hash| String::from("/") + hash);
2518- vec![
2519- (
2520- String::from("activity"),
2521- format!("/{}/{}/chart/activity{}", collection, name, commit_path),
2522- current_page == "activity",
2523- ),
2524- (
2525- String::from("languages"),
2526- format!("/{}/{}/chart/languages{}", collection, name, commit_path),
2527- current_page == "languages",
2528- ),
2529- ]
2530- }
2531 diff --git a/ayllu/src/web2/routes/about.rs b/ayllu/src/web2/routes/about.rs
2532index 60dc0d0..d855030 100644
2533--- a/ayllu/src/web2/routes/about.rs
2534+++ b/ayllu/src/web2/routes/about.rs
2535 @@ -1,28 +1,35 @@
2536- use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
2537-
2538+ use askama::Template;
2539 use axum::{extract::Extension, response::Html};
2540+ use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
2541
2542 use crate::config::Config;
2543 use crate::highlight::TreeSitterAdapter;
2544 use crate::web2::error::Error;
2545- use crate::web2::middleware::template::Template;
2546- use crate::web2::navigation;
2547+ use crate::web2::template::Base;
2548+
2549+ #[derive(askama::Template)]
2550+ #[template(path = "about.html")]
2551+ struct PageTemplate<'a> {
2552+ pub base: Base,
2553+ pub blurb: &'a str,
2554+ }
2555
2556 pub async fn serve(
2557 Extension(cfg): Extension<Config>,
2558 Extension(adapter): Extension<TreeSitterAdapter>,
2559- Extension((templates, mut ctx)): Extension<Template>,
2560+ Extension(mut base): Extension<Base>,
2561 ) -> Result<Html<String>, Error> {
2562- ctx.insert("title", "about");
2563- ctx.insert(
2564- "nav_elements",
2565- &navigation::global("about", cfg.mail.is_some()),
2566- );
2567 let options = ComrakOptions::default();
2568 let mut plugins = ComrakPlugins::default();
2569 plugins.render.codefence_syntax_highlighter = Some(&adapter);
2570 let blurb = markdown_to_html_with_plugins(&cfg.blurb, &options, &plugins);
2571- ctx.insert("blurb", &blurb);
2572- let body = templates.render("about.html", &ctx)?;
2573- Ok(Html(body))
2574+ base.nav_elements = crate::web2::navigation::global("about", false);
2575+ base.current_time = timeutil::timestamp();
2576+ Ok(Html(
2577+ PageTemplate {
2578+ base,
2579+ blurb: &blurb,
2580+ }
2581+ .render()?,
2582+ ))
2583 }
2584 diff --git a/ayllu/src/web2/routes/assets.rs b/ayllu/src/web2/routes/assets.rs
2585index 700baba..91ed34c 100644
2586--- a/ayllu/src/web2/routes/assets.rs
2587+++ b/ayllu/src/web2/routes/assets.rs
2588 @@ -1,134 +1,55 @@
2589- use std::path::Path;
2590- use std::sync::Arc;
2591-
2592- use axum::{
2593- body::{Body, Bytes},
2594- extract,
2595- http::header,
2596- response::Response,
2597- };
2598- use bytes::{BufMut, BytesMut};
2599- use mime;
2600- use mime_guess::MimeGuess;
2601- use serde::Deserialize;
2602- use tokio::fs::{read, File};
2603- use tokio_util::io::ReaderStream;
2604-
2605- use crate::config::Config;
2606- use crate::web2::error::Error;
2607 use crate::web2::extractors::config::ConfigReader;
2608- use crate::web2::terautil::{Assets, Themes};
2609+ use crate::{config::Config, web2::template::DEFAULT_THEME_CSS};
2610+ use axum::{extract, http::header::CONTENT_TYPE, response::Response};
2611
2612- pub type State = Arc<(Config, Themes)>;
2613-
2614- #[derive(Debug, Deserialize, Default)]
2615- pub struct Resource {
2616- pub name: String,
2617+ pub async fn serve_stylesheet(
2618+ extract::State(state): extract::State<Config>,
2619+ ConfigReader(user_config): ConfigReader,
2620+ ) -> Response<String> {
2621+ let theme_css = if let Some(current_theme) = user_config.theme {
2622+ let theme_content = state
2623+ .web
2624+ .themes
2625+ .iter()
2626+ .find(|theme| theme.name == current_theme)
2627+ .map(|theme| format!("{}\n\n{}", DEFAULT_THEME_CSS, theme.css_content))
2628+ .unwrap_or(DEFAULT_THEME_CSS.to_string());
2629+ theme_content
2630+ } else {
2631+ DEFAULT_THEME_CSS.to_string()
2632+ };
2633+ let mut res = Response::new(theme_css);
2634+ let headers = res.headers_mut();
2635+ headers.insert(CONTENT_TYPE, "text/css".parse().unwrap());
2636+ res
2637 }
2638
2639- async fn to_response(assets: &Assets, path: &str) -> Result<Response, Error> {
2640- let guess = MimeGuess::from_path(Path::new(path)).first_or_octet_stream();
2641- let content_type = guess.to_string();
2642- tracing::debug!("serving static asset {}: {}", content_type, path);
2643- if let Some(asset) = assets.0.get(path) {
2644- match asset {
2645- crate::web2::terautil::Asset::Raw(content) => {
2646- let bytes = Bytes::from(content.clone());
2647- Ok(Response::builder()
2648- .header(header::CONTENT_TYPE, content_type)
2649- .body(Body::from(bytes))
2650- .unwrap())
2651- }
2652- crate::web2::terautil::Asset::FilePath(path) => {
2653- let file = File::open(path).await?;
2654- let stream = ReaderStream::new(file);
2655- Ok(Response::builder()
2656- .header(header::CONTENT_TYPE, content_type)
2657- .body(Body::from_stream(stream))
2658- .unwrap())
2659- }
2660- }
2661- } else {
2662- Ok(Response::builder().status(404).body(Body::empty()).unwrap())
2663- }
2664+ pub async fn serve_rss_xslt(
2665+ extract::State(_state): extract::State<Config>,
2666+ ConfigReader(_user_config): ConfigReader,
2667+ ) -> Response<String> {
2668+ let mut res = Response::new(crate::web2::template::DEFAULT_RSS_XSLT_FILE.to_string());
2669+ let headers = res.headers_mut();
2670+ headers.insert(CONTENT_TYPE, "application/xml".parse().unwrap());
2671+ res
2672 }
2673
2674- pub async fn serve_stylesheet(
2675- extract::State(state): extract::State<State>,
2676- ConfigReader(user_config): ConfigReader,
2677- ) -> Result<Response, Error> {
2678- if let Some(theme_name) = user_config.theme {
2679- if let Some(theme) = state.1 .0.get(&theme_name) {
2680- tracing::debug!("serving alternative theme: {}", theme_name);
2681- let default_theme = state
2682- .1
2683- .0
2684- .get("default")
2685- .expect("default theme not configured");
2686- let base_stylesheet = default_theme
2687- .1
2688- .0
2689- .get("main.min.css")
2690- .expect("default stylesheet not configured");
2691- let base_stylesheet_bytes = match base_stylesheet {
2692- crate::web2::terautil::Asset::Raw(content) => content.clone(),
2693- crate::web2::terautil::Asset::FilePath(path) => read(path).await?,
2694- };
2695- if let Some(stylesheet) = theme.1 .0.get("theme.css") {
2696- let stylesheet = match stylesheet {
2697- crate::web2::terautil::Asset::Raw(content) => {
2698- let mut raw_theme = BytesMut::from(base_stylesheet_bytes.as_slice());
2699- raw_theme.put(&b"\n"[..]);
2700- raw_theme.put_slice(content.as_slice());
2701- raw_theme
2702- }
2703- crate::web2::terautil::Asset::FilePath(path) => {
2704- let mut raw_theme = BytesMut::from(base_stylesheet_bytes.as_slice());
2705- raw_theme.put(&b"\n"[..]);
2706- raw_theme.put_slice(read(path).await?.as_slice());
2707- // let mut raw_theme = read(path).await?.as_slice();
2708- raw_theme
2709- }
2710- };
2711- return Ok(Response::builder()
2712- .status(200)
2713- .header(header::CONTENT_TYPE, mime::TEXT_CSS_UTF_8.to_string())
2714- .body(Body::from(stylesheet.to_vec()))
2715- .unwrap());
2716- }
2717- }
2718- };
2719- to_response(
2720- &state
2721- .1
2722- .0
2723- .get("default")
2724- .expect("default theme not configured")
2725- .1,
2726- "main.min.css",
2727- )
2728- .await
2729+ pub async fn serve_logo(
2730+ extract::State(_state): extract::State<Config>,
2731+ ConfigReader(_user_config): ConfigReader,
2732+ ) -> Response<String> {
2733+ let mut res = Response::new(crate::web2::template::DEFAULT_LOGO.to_string());
2734+ let headers = res.headers_mut();
2735+ headers.insert(CONTENT_TYPE, "image/svg+xml".parse().unwrap());
2736+ res
2737 }
2738
2739- pub async fn serve_asset(
2740- extract::Path(resource): extract::Path<Resource>,
2741- extract::State(state): extract::State<State>,
2742- ConfigReader(user_config): ConfigReader,
2743- ) -> Result<Response, Error> {
2744- if let Some(theme_name) = user_config.theme {
2745- if let Some(theme) = state.1 .0.get(&theme_name) {
2746- tracing::debug!("serving alternative theme: {}", theme_name);
2747- return to_response(&theme.1, &resource.name).await;
2748- }
2749- };
2750- to_response(
2751- &state
2752- .1
2753- .0
2754- .get("default")
2755- .expect("default theme is not configured")
2756- .1,
2757- &resource.name,
2758- )
2759- .await
2760+ pub async fn serve_feed_icon(
2761+ extract::State(_state): extract::State<Config>,
2762+ ConfigReader(_user_config): ConfigReader,
2763+ ) -> Response<String> {
2764+ let mut res = Response::new(crate::web2::template::DEFAULT_RSS_ICON.to_string());
2765+ let headers = res.headers_mut();
2766+ headers.insert(CONTENT_TYPE, "image/svg+xml".parse().unwrap());
2767+ res
2768 }
2769 diff --git a/ayllu/src/web2/routes/authors.rs b/ayllu/src/web2/routes/authors.rs
2770deleted file mode 100644
2771index f0491fb..0000000
2772--- a/ayllu/src/web2/routes/authors.rs
2773+++ /dev/null
2774 @@ -1,32 +0,0 @@
2775- use std::sync::Arc;
2776-
2777- use axum::{extract::Extension, response::Html};
2778-
2779- use crate::web2::error::Error;
2780- use crate::web2::middleware::repository::Preamble;
2781- use crate::web2::middleware::template::Template;
2782- use crate::web2::navigation;
2783-
2784- use ayllu_database::{contributors, Wrapper as Database};
2785-
2786- pub async fn serve(
2787- Extension(db): Extension<Arc<Database>>,
2788- Extension(preamble): Extension<Preamble>,
2789- Extension((templates, mut ctx)): Extension<Template>,
2790- ) -> Result<Html<String>, Error> {
2791- // TODO: move to extension
2792- let authors = contributors::Database(&db)
2793- .authors_list(
2794- preamble.repo_path_string().as_str(),
2795- preamble.latest_commit_id.clone().unwrap().as_str(),
2796- )
2797- .await?;
2798- ctx.insert("title", &format!("{} Authors", preamble.repo_name.clone()));
2799- ctx.insert(
2800- "nav_elements",
2801- &navigation::primary("authors", &preamble.collection_name, &preamble.repo_name),
2802- );
2803- ctx.insert("authors", &authors);
2804- let body = templates.render("authors.html", &ctx)?;
2805- Ok(Html(body))
2806- }
2807 diff --git a/ayllu/src/web2/routes/blame.rs b/ayllu/src/web2/routes/blame.rs
2808deleted file mode 100644
2809index b219600..0000000
2810--- a/ayllu/src/web2/routes/blame.rs
2811+++ /dev/null
2812 @@ -1,84 +0,0 @@
2813- use serde::Serialize;
2814-
2815- use axum::{extract::Extension, response::Html};
2816-
2817- use crate::config::Config;
2818- use crate::highlight::Highlighter;
2819- use crate::time as timeutil;
2820- use crate::web2::error::Error;
2821- use crate::web2::middleware::repository::Preamble;
2822- use crate::web2::middleware::template::Template;
2823- use crate::web2::navigation;
2824- use ayllu_git::Wrapper;
2825-
2826- #[derive(Serialize, Default, Clone)]
2827- struct BlameItem {
2828- start: usize,
2829- end: usize,
2830- author_name: String,
2831- author_email: String,
2832- timestamp: String,
2833- commit_id: String,
2834- }
2835-
2836- pub async fn serve(
2837- Extension(_cfg): Extension<Config>,
2838- Extension(preamble): Extension<Preamble>,
2839- Extension(highlighter): Extension<Highlighter>,
2840- Extension((templates, mut ctx)): Extension<Template>,
2841- ) -> Result<Html<String>, Error> {
2842- let repository = Wrapper::new(preamble.repo_path.as_path())?;
2843- let title = format!("blame: {}", preamble.file_name());
2844- ctx.insert("title", &title);
2845- ctx.insert(
2846- "nav_elements",
2847- &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
2848- );
2849- ctx.insert("file_name", &preamble.file_name());
2850- ctx.insert(
2851- "subnav_elements",
2852- &navigation::subnav(
2853- "blame",
2854- &preamble.collection_name,
2855- &preamble.repo_name,
2856- &preamble.file_path_string(),
2857- &preamble.refname,
2858- &preamble.latest_commit_id.clone().unwrap_or(String::new()),
2859- ),
2860- );
2861- ctx.insert("subtitle", "blame");
2862- let file_path = preamble.file_path.clone().unwrap();
2863- if let Some(blob) = repository.blob(file_path.as_path(), preamble.latest_commit_id.clone())? {
2864- if blob.is_binary {
2865- ctx.insert("is_renderable", &false);
2866- } else {
2867- let content = highlighter.highlight_vec(
2868- blob.content,
2869- Some(file_path.to_str().unwrap()),
2870- None,
2871- None,
2872- false,
2873- );
2874- ctx.insert("is_renderable", &true);
2875- ctx.insert("content", &content);
2876- let lines = repository.blame(
2877- preamble.file_path.clone().unwrap().as_path(),
2878- preamble.latest_commit_id.clone(),
2879- )?;
2880- let lines: Vec<BlameItem> = lines
2881- .iter()
2882- .map(|item| BlameItem {
2883- start: item.start,
2884- end: item.end,
2885- author_name: item.author_name.clone(),
2886- author_email: item.author_email.clone(),
2887- timestamp: timeutil::friendly(item.timestamp as u64),
2888- commit_id: item.commit_id.clone(),
2889- })
2890- .collect();
2891- ctx.insert("blame_lines", &lines);
2892- }
2893- }
2894- let body = templates.render("blame.html", &ctx)?;
2895- Ok(Html(body))
2896- }
2897 diff --git a/ayllu/src/web2/routes/blob.rs b/ayllu/src/web2/routes/blob.rs
2898index 6fdb5f5..5868a4d 100644
2899--- a/ayllu/src/web2/routes/blob.rs
2900+++ b/ayllu/src/web2/routes/blob.rs
2901 @@ -1,5 +1,4 @@
2902- use std::time::SystemTime;
2903-
2904+ use askama::Template;
2905 use axum::{
2906 body::Bytes,
2907 extract::Extension,
2908 @@ -7,17 +6,45 @@ use axum::{
2909 response::{Html, IntoResponse, Redirect, Response},
2910 };
2911
2912- use crate::config::Config;
2913- use crate::highlight::{Highlighter, TreeSitterAdapter};
2914 use crate::languages::LANGUAGE_TABLE;
2915 use crate::readme::Renderer;
2916 use crate::web2::error::Error;
2917 use crate::web2::middleware::repository::Preamble;
2918- use crate::web2::middleware::template::Template;
2919 use crate::web2::navigation;
2920- use crate::web2::terautil::Theme;
2921+ use crate::web2::template::filters;
2922 use crate::web2::util;
2923- use ayllu_git::Wrapper;
2924+ use crate::{
2925+ config::Config,
2926+ languages::Hint,
2927+ web2::{
2928+ navigation::Items,
2929+ template::{Base, DEFAULT_ANCHOR_SYMBOL},
2930+ },
2931+ };
2932+ use crate::{
2933+ highlight::{Highlighter, TreeSitterAdapter},
2934+ with_preamble,
2935+ };
2936+ use ayllu_git::{Blob, Wrapper};
2937+
2938+ #[derive(Template)]
2939+ #[template(path = "blob.html")]
2940+ struct PageTemplate<'a> {
2941+ pub base: Base,
2942+ pub subnav_elements: Items,
2943+ pub file_name: &'a str,
2944+ pub file_size: f64,
2945+ pub file_mode_: i32,
2946+ pub mime_type: &'a str,
2947+ pub is_image: bool,
2948+ pub is_video: bool,
2949+ pub is_binary: bool,
2950+ pub is_audio: bool,
2951+ pub is_markdown: bool,
2952+ pub content: Option<&'a str>,
2953+ pub hint: Option<&'a (Hint, String)>,
2954+ pub raw_url: &'a str,
2955+ }
2956
2957 // TODO: considerable clean up is needed here
2958 pub async fn serve(
2959 @@ -25,8 +52,7 @@ pub async fn serve(
2960 Extension(preamble): Extension<Preamble>,
2961 Extension(highlighter): Extension<Highlighter>,
2962 Extension(adapter): Extension<TreeSitterAdapter>,
2963- Extension(current_theme): Extension<Theme>,
2964- Extension((templates, mut ctx)): Extension<Template>,
2965+ Extension(mut base): Extension<Base>,
2966 ) -> Result<Html<String>, Error> {
2967 let repository = Wrapper::new(preamble.repo_path.as_path())?;
2968 let blob = repository.blob(
2969 @@ -37,66 +63,49 @@ pub async fn serve(
2970 return Err(Error::NotFound(String::from("Not Found")));
2971 }
2972 let blob = blob.unwrap();
2973- ctx.insert("title", &preamble.file_name());
2974- ctx.insert("file_name", &preamble.file_name());
2975- ctx.insert("blob", &blob.drop_content());
2976- ctx.insert("file_size", &blob.size);
2977- ctx.insert("file_mode", &blob.mode);
2978- let mut is_markdown = false;
2979+ base.title = preamble.file_name();
2980+ base.nav_elements = navigation::primary("blob", &preamble.collection_name, &preamble.repo_name);
2981 let mime_type = mime_guess::from_path(preamble.file_path_string()).first_or_octet_stream();
2982- ctx.insert("mime_type", &mime_type.to_string());
2983- ctx.insert("subtitle", "blob");
2984- log::debug!("rendering blob with mime type: {}", mime_type.to_string());
2985+ log::debug!("rendering blob with mime type: {}", mime_type);
2986+ let mut content: Option<String> = None;
2987+ let mut is_image = false;
2988+ let mut is_video = false;
2989+ let mut is_binary = false;
2990+ let mut is_audio = false;
2991+ let mut is_markdown = false;
2992+ let mut hint: Option<(Hint, String)> = None;
2993 if mime_type.type_() == "image" {
2994- ctx.insert("is_image", &true);
2995- ctx.insert("is_binary", &true);
2996- ctx.insert("content", &None::<()>);
2997+ is_binary = true;
2998+ is_image = true;
2999 } else if mime_type.type_() == "video" {
3000- ctx.insert("is_video", &true);
3001- ctx.insert("is_binary", &true);
3002- ctx.insert("content", &None::<()>);
3003+ is_binary = true;
3004+ is_video = true;
3005 } else if mime_type.type_() == "audio" {
3006- ctx.insert("is_audio", &true);
3007- ctx.insert("is_binary", &true);
3008- ctx.insert("content", &None::<()>);
3009- } else if mime_type.to_string().as_str() == "text/markdown" {
3010- // find the anchor symbol in the current theme
3011- let anchor_symbol = current_theme
3012- .1
3013- .0
3014- .get("anchor.svg")
3015- .expect("missing default asset");
3016-
3017- let anchor_symbol = anchor_symbol.read_string().await?;
3018+ is_binary = true;
3019+ is_audio = true;
3020+ } else if mime_type == "text/markdown" {
3021+ is_markdown = true;
3022 let renderer = Renderer {
3023 origin: &cfg.origin,
3024 collection: &preamble.collection_name,
3025 name: &preamble.repo_name,
3026 refname: &preamble.refname,
3027- anchor_symbol: &anchor_symbol,
3028+ anchor_symbol: DEFAULT_ANCHOR_SYMBOL,
3029 allow_unsafe_markdown: cfg.web.unsafe_markdown,
3030 };
3031 let readme = String::from_utf8(blob.content).unwrap();
3032- let content = renderer.render(adapter, &readme);
3033- ctx.insert("content", &content);
3034- is_markdown = true;
3035- } else if blob.is_binary {
3036- ctx.insert("content", &None::<()>);
3037+ content = Some(renderer.render(adapter, &readme));
3038 } else {
3039- let content = String::from_utf8(blob.content).unwrap();
3040+ let blob_content = String::from_utf8(blob.content).unwrap();
3041 let file_path = preamble.file_path_string();
3042- let (hint, content) =
3043- highlighter.highlight(&content, Some(file_path.as_str()), None, None, true);
3044- if let Some(hint) = hint {
3045- ctx.insert("color", &LANGUAGE_TABLE.color_from_language(&hint));
3046- ctx.insert("hint", &hint);
3047+ let (chint, highlighted) =
3048+ highlighter.highlight(&blob_content, Some(file_path.as_str()), None, None, true);
3049+ if let Some(chint) = chint {
3050+ let color = LANGUAGE_TABLE.color_from_language(&chint);
3051+ hint = Some((chint, color))
3052 }
3053- ctx.insert("content", &content);
3054+ content = Some(highlighted);
3055 }
3056- ctx.insert(
3057- "nav_elements",
3058- &navigation::primary("blob", &preamble.collection_name, &preamble.repo_name),
3059- );
3060 let subnav = navigation::subnav(
3061 "blob",
3062 &preamble.collection_name,
3063 @@ -105,15 +114,33 @@ pub async fn serve(
3064 &preamble.refname,
3065 &preamble.latest_commit_id.clone().unwrap_or(String::new()),
3066 );
3067- ctx.insert("raw_url", &subnav.get(4).unwrap().1);
3068- ctx.insert("subnav_elements", &subnav);
3069- ctx.insert("file_type", &mime_type.to_string());
3070- ctx.insert("is_markdown", &is_markdown);
3071- let end = SystemTime::now();
3072- let duration = end.duration_since(preamble.start_time).unwrap();
3073- ctx.insert("render_time", &duration.as_millis());
3074- let body = templates.render("blob.html", &ctx)?;
3075- Ok(Html(body))
3076+
3077+ let raw_url = subnav.get(3).unwrap().1.clone();
3078+
3079+ let file_size = blob.size;
3080+ let file_mode = blob.mode;
3081+
3082+ with_preamble!(base, preamble);
3083+
3084+ Ok(Html(
3085+ PageTemplate {
3086+ base,
3087+ subnav_elements: subnav,
3088+ file_size: (file_size as f64),
3089+ file_mode_: file_mode,
3090+ file_name: &preamble.file_name(),
3091+ mime_type: &mime_type.to_string(),
3092+ is_image,
3093+ is_video,
3094+ is_binary,
3095+ is_audio,
3096+ is_markdown,
3097+ content: content.as_deref(),
3098+ hint: hint.as_ref(),
3099+ raw_url: &raw_url,
3100+ }
3101+ .render()?,
3102+ ))
3103 }
3104
3105 pub async fn serve_raw(
3106 @@ -153,7 +180,7 @@ pub async fn serve_raw(
3107 log::debug!(
3108 "serving blob {} with mime {}",
3109 preamble.file_path_string(),
3110- mime.to_string()
3111+ mime
3112 );
3113 let mut response = Bytes::from(blob.content.clone()).into_response();
3114 response.headers_mut().insert(
3115 diff --git a/ayllu/src/web2/routes/builds.rs b/ayllu/src/web2/routes/builds.rs
3116deleted file mode 100644
3117index 21a722f..0000000
3118--- a/ayllu/src/web2/routes/builds.rs
3119+++ /dev/null
3120 @@ -1,90 +0,0 @@
3121- use axum::{
3122- body::Bytes,
3123- extract::Extension,
3124- http::header::{HeaderValue, CONTENT_TYPE},
3125- response::{Html, IntoResponse, Response},
3126- };
3127- use serde::Serialize;
3128-
3129- use crate::config::Config;
3130- use crate::web2::error::Error;
3131-
3132- use crate::web2::middleware::template::Template;
3133-
3134- #[derive(Debug, Serialize)]
3135- struct Build {
3136- id: String,
3137- timestamp: String,
3138- runtime: f64,
3139- success: bool,
3140- }
3141-
3142- // #[derive(Debug, Serialize)]
3143- // struct Details {
3144- // build: Build,
3145- // steps: Vec<(String, String, String)>,
3146- // }
3147-
3148- pub async fn serve(
3149- Extension(_cfg): Extension<Config>,
3150- Extension((templates, mut ctx)): Extension<Template>,
3151- ) -> Result<Html<String>, Error> {
3152- ctx.insert("title", "builds");
3153- // TODO
3154- let builds = vec![
3155- Build {
3156- id: String::from("1234"),
3157- timestamp: String::from("1999-01-02"),
3158- runtime: 25.0,
3159- success: true,
3160- },
3161- Build {
3162- id: String::from("4567"),
3163- timestamp: String::from("1999-02-01"),
3164- runtime: 29.0,
3165- success: false,
3166- },
3167- ];
3168- ctx.insert("builds", &builds);
3169- let body = templates.render("builds.html", &ctx)?;
3170- Ok(Html(body))
3171- }
3172-
3173- pub async fn details(
3174- Extension(_cfg): Extension<Config>,
3175- Extension((templates, mut ctx)): Extension<Template>,
3176- ) -> Result<Html<String>, Error> {
3177- ctx.insert("title", "builds");
3178- ctx.insert(
3179- "build",
3180- &Build {
3181- id: String::from("1234"),
3182- timestamp: String::from("1999-01-01"),
3183- runtime: 25.0,
3184- success: true,
3185- },
3186- );
3187- let build_steps: Vec<(String, String, String)> = vec![(
3188- String::from("phase_1"),
3189- String::from("stdout\n\nfuu\nbar\n"),
3190- String::from("stderr\n\n\nerror\n"),
3191- )];
3192- ctx.insert("steps", &build_steps);
3193- let body = templates.render("build.html", &ctx)?;
3194- Ok(Html(body))
3195- }
3196-
3197- pub async fn badge(
3198- Extension(_cfg): Extension<Config>,
3199- Extension((templates, mut ctx)): Extension<Template>,
3200- ) -> Result<Response, Error> {
3201- ctx.insert("key", "build");
3202- ctx.insert("value", "success");
3203- let body = templates.render("badge.svg", &ctx)?;
3204- let mut response = Bytes::from(body).into_response();
3205- response.headers_mut().insert(
3206- CONTENT_TYPE,
3207- HeaderValue::from_str("image/svg+xml").unwrap(),
3208- );
3209- Ok(response)
3210- }
3211 diff --git a/ayllu/src/web2/routes/chart.rs b/ayllu/src/web2/routes/chart.rs
3212deleted file mode 100644
3213index 682ea42..0000000
3214--- a/ayllu/src/web2/routes/chart.rs
3215+++ /dev/null
3216 @@ -1,110 +0,0 @@
3217- use std::sync::Arc;
3218-
3219- use axum::{
3220- extract::{Extension, Path},
3221- response::Html,
3222- };
3223- use serde::Deserialize;
3224- use time::Duration;
3225-
3226- use crate::web2::charts::Renderer;
3227- use crate::web2::error::Error;
3228- use crate::web2::middleware::repository::Preamble;
3229- use crate::web2::middleware::template::Template;
3230- use crate::web2::navigation;
3231-
3232- use ayllu_database::{languages, stats, stats::Aggregation, Wrapper as Database};
3233-
3234- const MAXIMUM_SIZE: u32 = 2000;
3235- const DEFAULT_HEIGHT: u32 = 450;
3236- const DEFAULT_WIDTH: u32 = 600;
3237-
3238- pub const EMPTY_IMAGE: &str = r#"
3239- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>
3240- "#;
3241-
3242- #[derive(Deserialize)]
3243- pub struct Params {
3244- pub commit_id: Option<String>,
3245- pub kind: Option<String>,
3246- pub height: Option<u32>,
3247- pub width: Option<u32>,
3248- }
3249-
3250- pub async fn serve(
3251- Path(params): Path<Params>,
3252- Extension(db): Extension<Arc<Database>>,
3253- Extension(preamble): Extension<Preamble>,
3254- Extension((templates, mut ctx)): Extension<Template>,
3255- ) -> Result<Html<String>, Error> {
3256- let git_hash = match params.commit_id.clone() {
3257- Some(git_hash) => Some(git_hash),
3258- None => preamble.latest_commit_id.clone(),
3259- };
3260-
3261- let chart_kind = params.kind.unwrap_or(String::from("activity"));
3262- let width = params.width.map_or(DEFAULT_WIDTH, |width| {
3263- if width > MAXIMUM_SIZE {
3264- DEFAULT_WIDTH
3265- } else {
3266- width
3267- }
3268- });
3269- let height = params.height.map_or(DEFAULT_HEIGHT, |height| {
3270- if height > MAXIMUM_SIZE {
3271- DEFAULT_HEIGHT
3272- } else {
3273- height
3274- }
3275- });
3276-
3277- ctx.insert(
3278- "nav_elements",
3279- &navigation::primary("charts", &preamble.collection_name, &preamble.repo_name),
3280- );
3281- ctx.insert(
3282- "chartnav",
3283- &navigation::chartnav(
3284- &chart_kind,
3285- &preamble.collection_name,
3286- &preamble.repo_name,
3287- params.commit_id.as_deref(),
3288- ),
3289- );
3290- if let Some(git_hash) = git_hash {
3291- // DB based operations
3292- // TODO: move to extension
3293- let chart_builder = Renderer::new((width, height));
3294- let xml = match chart_kind.as_str() {
3295- "activity" => {
3296- ctx.insert("title", "Activity");
3297- ctx.insert("chart_title", "Activity");
3298- let data = stats::Database(&db)
3299- .contribution_buckets_for_repo(
3300- &preamble.repo_path_string(),
3301- &git_hash,
3302- Aggregation::Day,
3303- 90 * Duration::DAY,
3304- )
3305- .await?;
3306- chart_builder.activity(&data)?
3307- }
3308- "languages" => {
3309- ctx.insert("title", "Languages");
3310- ctx.insert("chart_title", "Language Composition");
3311- let data = languages::Database(&db)
3312- .languages_list(&preamble.repo_path_string(), &git_hash)
3313- .await?;
3314- chart_builder.languages(&data)?
3315- }
3316- name => return Err(Error::Message(format!("unknown chart kind: {}", name))),
3317- };
3318- ctx.insert("chart", &xml);
3319- } else {
3320- ctx.insert("title", "empty");
3321- ctx.insert("chart_title", "empty");
3322- ctx.insert("chart", EMPTY_IMAGE);
3323- };
3324- let body = templates.render("chart.html", &ctx)?;
3325- Ok(Html(body))
3326- }
3327 diff --git a/ayllu/src/web2/routes/commit.rs b/ayllu/src/web2/routes/commit.rs
3328index 84cfb6c..d3e7c0f 100644
3329--- a/ayllu/src/web2/routes/commit.rs
3330+++ b/ayllu/src/web2/routes/commit.rs
3331 @@ -1,42 +1,65 @@
3332+ use askama::Template;
3333 use axum::{
3334 extract::{Extension, Path},
3335 response::Html,
3336 };
3337
3338- use crate::highlight::Highlighter;
3339- use crate::web2::error::Error;
3340+ use crate::{web2::template::filters, with_preamble};
3341 use crate::web2::middleware::repository::Preamble;
3342- use crate::web2::middleware::template::Template;
3343 use crate::web2::navigation;
3344- use ayllu_git::Wrapper;
3345+ use crate::{highlight::Highlighter, web2::template::Base};
3346+ use crate::{languages::Hint, web2::error::Error};
3347+ use ayllu_git::{Commit, Note, Stats, Wrapper};
3348+
3349+ #[derive(Template)]
3350+ #[template(path = "commit.html")]
3351+ struct PageTemplate<'a> {
3352+ pub base: Base,
3353+ pub collection: &'a str,
3354+ pub name: &'a str,
3355+ pub commit_hash: &'a str,
3356+ pub note: Option<&'a Note>,
3357+ pub commit: &'a Commit,
3358+ pub stats: &'a Stats,
3359+ pub distinct_author: bool,
3360+ pub extended_commit_message: bool,
3361+ pub diff: (Option<&'a Hint>, &'a str),
3362+ }
3363
3364 pub async fn serve(
3365 Path((_, _, commit_id)): Path<(String, String, String)>,
3366 Extension(preamble): Extension<Preamble>,
3367 Extension(highlighter): Extension<Highlighter>,
3368- Extension((templates, mut ctx)): Extension<Template>,
3369+ Extension(mut base): Extension<Base>,
3370 ) -> Result<Html<String>, Error> {
3371 let repository = Wrapper::new(preamble.repo_path.as_path())?;
3372- let title = format!("commit {}", commit_id);
3373- ctx.insert("title", &title);
3374- ctx.insert(
3375- "nav_elements",
3376- &navigation::primary("", &preamble.collection_name, &preamble.repo_name),
3377- );
3378- ctx.insert("commit_hash", &commit_id);
3379+ base.title = format!("Commit: {}", commit_id);
3380+ base.nav_elements =
3381+ navigation::primary("commit", &preamble.collection_name, &preamble.repo_name);
3382 let commit = repository.commit(Some(commit_id.to_string()))?.unwrap();
3383- if commit.has_note.is_some_and(|has_note| has_note) {
3384- let note = repository.read_note(commit.id.as_str())?;
3385- ctx.insert("note", &note);
3386- }
3387+ let note = if commit.has_note.is_some_and(|has_note| has_note) {
3388+ Some(repository.read_note(commit.id.as_str())?)
3389+ } else {
3390+ None
3391+ };
3392 let (stats, diff) = repository.diff(&commit_id)?;
3393- ctx.insert("commit", &commit);
3394- ctx.insert("stats", &stats);
3395- ctx.insert("distinct_author", &commit.distinct_author());
3396- ctx.insert("extended_commit_message", &commit.extended_commit_message());
3397 let formatted_diff =
3398 highlighter.highlight(diff.as_str(), None, None, Some("diff".into()), true);
3399- ctx.insert("diff", &formatted_diff);
3400- let body = templates.render("commit.html", &ctx)?;
3401- Ok(Html(body))
3402+ // ctx.insert("diff", &formatted_diff);
3403+ with_preamble!(base, preamble);
3404+ Ok(Html(
3405+ PageTemplate {
3406+ base,
3407+ collection: &preamble.collection_name,
3408+ name: &preamble.repo_name,
3409+ commit_hash: &commit.id,
3410+ note: note.as_ref(),
3411+ commit: &commit,
3412+ stats: &stats,
3413+ distinct_author: commit.distinct_author(),
3414+ extended_commit_message: commit.extended_commit_message(),
3415+ diff: (formatted_diff.0.as_ref(), &formatted_diff.1),
3416+ }
3417+ .render()?,
3418+ ))
3419 }
3420 diff --git a/ayllu/src/web2/routes/config.rs b/ayllu/src/web2/routes/config.rs
3421index 788efba..3d4f92f 100644
3422--- a/ayllu/src/web2/routes/config.rs
3423+++ b/ayllu/src/web2/routes/config.rs
3424 @@ -1,3 +1,4 @@
3425+ use askama::Template;
3426 use axum::{
3427 extract::{Extension, Form},
3428 response::{Html, Redirect},
3429 @@ -5,28 +6,39 @@ use axum::{
3430
3431 use axum_extra::extract::cookie::{Cookie, CookieJar};
3432
3433- use crate::config::Config as SystemConfig;
3434 use crate::web2::config::Config;
3435 use crate::web2::error::Error;
3436 use crate::web2::extractors::config::ConfigReader;
3437- use crate::web2::middleware::template::Template;
3438- use crate::web2::navigation;
3439+ use crate::{config::Config as SystemConfig, web2::template::Base};
3440+
3441+ #[derive(Template)]
3442+ #[template(path = "config.html")]
3443+ struct Index {
3444+ base: Base,
3445+ themes: Vec<String>,
3446+ config: Config,
3447+ }
3448
3449 pub async fn serve(
3450 Extension(cfg): Extension<SystemConfig>,
3451- Extension(themes): Extension<Vec<String>>,
3452- ConfigReader(client_config): ConfigReader,
3453- Extension((templates, mut ctx)): Extension<Template>,
3454+ ConfigReader(config): ConfigReader,
3455+ Extension(mut base): Extension<Base>,
3456 ) -> Result<Html<String>, Error> {
3457- ctx.insert("title", "config");
3458- ctx.insert("config", &client_config);
3459- ctx.insert("themes", &themes);
3460- ctx.insert(
3461- "nav_elements",
3462- &navigation::global("config", cfg.mail.is_some()),
3463- );
3464- let body = templates.render("config.html", &ctx)?;
3465- Ok(Html(body))
3466+ base.with_nav(&[("about", "/about", false), ("config", "/config", true)]);
3467+ base.current_time = timeutil::timestamp();
3468+ Ok(Html(
3469+ Index {
3470+ base,
3471+ themes: cfg
3472+ .web
3473+ .themes
3474+ .iter()
3475+ .map(|theme| theme.name.clone())
3476+ .collect(),
3477+ config,
3478+ }
3479+ .render()?,
3480+ ))
3481 }
3482
3483 pub async fn update(
3484 diff --git a/ayllu/src/web2/routes/index.rs b/ayllu/src/web2/routes/index.rs
3485index 9ab5455..7219a35 100644
3486--- a/ayllu/src/web2/routes/index.rs
3487+++ b/ayllu/src/web2/routes/index.rs
3488 @@ -1,5 +1,6 @@
3489 use std::convert::From;
3490
3491+ use askama::Template;
3492 use serde::Serialize;
3493
3494 use axum::{
3495 @@ -7,18 +8,15 @@ use axum::{
3496 response::Html,
3497 };
3498
3499- use crate::config::Config;
3500- use crate::time as timeutil;
3501 use crate::web2::error::Error;
3502- use crate::web2::middleware::template::Template;
3503- use crate::web2::navigation;
3504+ use crate::{config::Config, web2::template::Base};
3505 use ayllu_git::{Scanner, Wrapper};
3506
3507 #[derive(Debug, Serialize)]
3508 struct Collection {
3509- name: String,
3510- description: Option<String>,
3511- repositories: Vec<Repository>,
3512+ pub name: String,
3513+ pub description: Option<String>,
3514+ pub repositories: Vec<Repository>,
3515 }
3516
3517 #[derive(Debug, Serialize)]
3518 @@ -64,12 +62,17 @@ async fn load_repositories(collection_path: &str) -> Result<Vec<Repository>, Err
3519 Ok(repositories)
3520 }
3521
3522+ #[derive(Template)]
3523+ #[template(path = "index.html")]
3524+ struct IndexPageTemplate {
3525+ base: Base,
3526+ collections: Vec<Collection>,
3527+ }
3528+
3529 pub async fn index(
3530 Extension(cfg): Extension<Config>,
3531- Extension((templates, mut ctx)): Extension<Template>,
3532+ Extension(mut base): Extension<Base>,
3533 ) -> Result<Html<String>, Error> {
3534- // database::test_cache(config.database.as_str()).await?;
3535- ctx.insert("title", "repository listing");
3536 let mut collections: Vec<Collection> = Vec::new();
3537 for collection in cfg.collections.iter() {
3538 if collection.hidden.is_some_and(|hidden| hidden) {
3539 @@ -82,17 +85,25 @@ pub async fn index(
3540 repositories,
3541 });
3542 }
3543- ctx.insert("title", "ayllu");
3544- ctx.insert("collections", &collections);
3545- ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
3546- let body = templates.render("index.html", &ctx)?;
3547- Ok(Html(body))
3548+ base.title = String::from("Repositories");
3549+ base.nav_elements = crate::web2::navigation::global("index", false);
3550+ base.current_time = timeutil::timestamp();
3551+ let page = IndexPageTemplate { base, collections };
3552+ Ok(Html(page.render()?))
3553+ }
3554+
3555+ #[derive(Template)]
3556+ #[template(path = "collection.html")]
3557+ struct CollectionPageTemplate {
3558+ base: Base,
3559+ collection: Collection,
3560+ is_hidden: bool,
3561 }
3562
3563 pub async fn collection(
3564 Extension(cfg): Extension<Config>,
3565 collection: Option<Path<String>>,
3566- Extension((templates, mut ctx)): Extension<Template>,
3567+ Extension(mut base): Extension<Base>,
3568 ) -> Result<Html<String>, Error> {
3569 let entry = cfg.collections.iter().find(|entry| {
3570 collection
3571 @@ -104,11 +115,17 @@ pub async fn collection(
3572 }
3573 let entry = entry.unwrap();
3574 let repositories = load_repositories(entry.path.as_str()).await?;
3575- ctx.insert("title", "ayllu");
3576- ctx.insert("nav_elements", &navigation::global("", cfg.mail.is_some()));
3577- ctx.insert("collection", &entry.clone());
3578- ctx.insert("repositories", &repositories);
3579- ctx.insert("is_hidden", &entry.hidden.is_some_and(|x| x));
3580- let body = templates.render("collection.html", &ctx)?;
3581- Ok(Html(body))
3582+ base.title = format!("{:?}", collection);
3583+ base.nav_elements = crate::web2::navigation::global("index", false);
3584+ base.current_time = timeutil::timestamp();
3585+ let page = CollectionPageTemplate {
3586+ base,
3587+ collection: Collection {
3588+ name: entry.name.clone(),
3589+ description: entry.description.clone(),
3590+ repositories,
3591+ },
3592+ is_hidden: entry.hidden.is_some_and(|hidden| hidden),
3593+ };
3594+ Ok(Html(page.render()?))
3595 }
3596 diff --git a/ayllu/src/web2/routes/log.rs b/ayllu/src/web2/routes/log.rs
3597index 3cdf57d..c872752 100644
3598--- a/ayllu/src/web2/routes/log.rs
3599+++ b/ayllu/src/web2/routes/log.rs
3600 @@ -1,61 +1,76 @@
3601 use std::collections::HashMap;
3602
3603+ use askama::Template;
3604 use axum::{
3605 debug_handler,
3606 extract::{Extension, Query},
3607 response::Html,
3608 };
3609
3610- use crate::web2::error::Error;
3611- use crate::web2::extractors::config::ConfigReader;
3612- use crate::web2::middleware::repository::Preamble;
3613- use crate::web2::middleware::template::Template;
3614 use crate::web2::navigation;
3615- use ayllu_git::{Selector, Wrapper};
3616+ use crate::web2::template::filters;
3617+ use crate::web2::{error::Error, template::Base};
3618+ use crate::web2::{extractors::config::ConfigReader, navigation::Items};
3619+ use crate::{web2::middleware::repository::Preamble, with_preamble};
3620+ use ayllu_git::{Commit, Selector, Wrapper};
3621+
3622+ #[derive(Template)]
3623+ #[template(path = "log.html")]
3624+ struct PageTemplate<'a> {
3625+ base: Base,
3626+ collection: &'a str,
3627+ name: &'a str,
3628+ subtitle: &'a str,
3629+ file_view: bool,
3630+ file_name: &'a str,
3631+ subnav_elements: Items,
3632+ commits: Vec<Commit>,
3633+ has_more: bool,
3634+ last_commit: Option<Commit>,
3635+ }
3636
3637 #[debug_handler]
3638 pub async fn serve(
3639 Extension(preamble): Extension<Preamble>,
3640- Extension((templates, mut ctx)): Extension<Template>,
3641 ConfigReader(config): ConfigReader,
3642+ Extension(mut base): Extension<Base>,
3643 params: Option<Query<HashMap<String, String>>>,
3644 ) -> Result<Html<String>, Error> {
3645 let repository = Wrapper::new(preamble.repo_path.as_path())?;
3646- ctx.insert(
3647- "nav_elements",
3648- &navigation::primary("log", &preamble.collection_name, &preamble.repo_name),
3649+ base.nav_elements =
3650+ crate::web2::navigation::primary("log", &preamble.collection_name, &preamble.repo_name);
3651+ let subnav_elements = navigation::subnav(
3652+ "log",
3653+ &preamble.collection_name,
3654+ &preamble.repo_name,
3655+ &preamble.file_path_string(),
3656+ &preamble.refname,
3657+ &preamble.latest_commit_id.clone().unwrap_or(String::new()),
3658 );
3659- ctx.insert("file_view", &false);
3660- match preamble.file_path.clone() {
3661+ let tmpl = match preamble.file_path.as_ref() {
3662 Some(file_path) => {
3663- let title = format!("log: {}", preamble.file_name());
3664- ctx.insert("title", &title);
3665- ctx.insert("subtitle", &title);
3666- ctx.insert("file_view", &true);
3667- ctx.insert(
3668- "file_name",
3669- &file_path
3670- .file_name()
3671- .map_or(String::new(), |item| item.to_str().unwrap().to_string()),
3672- );
3673- ctx.insert(
3674- "subnav_elements",
3675- &navigation::subnav(
3676- "log",
3677- &preamble.collection_name,
3678- &preamble.repo_name,
3679- &preamble.file_path_string(),
3680- &preamble.refname,
3681- &preamble.latest_commit_id.clone().unwrap_or(String::new()),
3682- ),
3683- );
3684+ let title = format!("Log: {}", preamble.file_name());
3685+ base.title = title.clone();
3686 let commits =
3687 repository.log_path(file_path.as_path(), preamble.latest_commit_id.clone())?;
3688- ctx.insert("commits", &commits);
3689+ with_preamble!(base, preamble);
3690+ PageTemplate {
3691+ base,
3692+ name: &preamble.repo_name,
3693+ collection: &preamble.collection_name,
3694+ subtitle: &title.clone(),
3695+ file_view: true,
3696+ subnav_elements,
3697+ file_name: &file_path
3698+ .file_name()
3699+ .map_or(String::default(), |item| item.to_string_lossy().to_string()),
3700+ has_more: commits.len() == config.items_per_page,
3701+ last_commit: commits.last().cloned(),
3702+ commits,
3703+ }
3704 }
3705 None => {
3706- let title = format!("{} log", preamble.repo_name.clone());
3707- ctx.insert("title", &title);
3708+ base.title = format!("Log: {}", preamble.repo_name);
3709 let commits = repository.log(Selector {
3710 commit: preamble.latest_commit_id.clone(),
3711 username: params
3712 @@ -66,10 +81,20 @@ pub async fn serve(
3713 .and_then(|params| params.get("email").cloned()),
3714 limit: Some(config.items_per_page as i64),
3715 })?;
3716- ctx.insert("commits", &commits);
3717- ctx.insert("has_more", &(commits.len() == config.items_per_page));
3718+ with_preamble!(base, preamble);
3719+ PageTemplate {
3720+ base,
3721+ name: &preamble.repo_name,
3722+ collection: &preamble.collection_name,
3723+ subtitle: "",
3724+ file_name: "",
3725+ file_view: false,
3726+ subnav_elements,
3727+ has_more: commits.len() == config.items_per_page,
3728+ last_commit: commits.last().cloned(),
3729+ commits,
3730+ }
3731 }
3732- }
3733- let body = templates.render("log.html", &ctx)?;
3734- Ok(Html(body))
3735+ };
3736+ Ok(Html(tmpl.render()?))
3737 }
3738 diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs
3739deleted file mode 100644
3740index 06a2db1..0000000
3741--- a/ayllu/src/web2/routes/mail.rs
3742+++ /dev/null
3743 @@ -1,115 +0,0 @@
3744- use std::sync::Arc;
3745-
3746- use axum::{
3747- debug_handler,
3748- extract::{Extension, Path},
3749- response::{Html, Response},
3750- };
3751- use ayllu_database::{mail, mail::ListId, Wrapper as Database};
3752- use serde::Deserialize;
3753-
3754- use crate::config::Config;
3755- use crate::web2::error::Error;
3756- use crate::web2::middleware::template::Template;
3757- use crate::web2::navigation;
3758-
3759- #[derive(Deserialize)]
3760- pub struct Params {
3761- pub list_name: String,
3762- pub thread_id: Option<String>,
3763- pub message_id: Option<String>,
3764- }
3765-
3766- pub async fn lists(
3767- Extension(cfg): Extension<Config>,
3768- Extension((templates, mut ctx)): Extension<Template>,
3769- ) -> Result<Html<String>, Error> {
3770- ctx.insert("title", "lists");
3771- ctx.insert("nav_elements", &navigation::global("mail", true));
3772- let mail_cfg = cfg
3773- .mail
3774- .ok_or(Error::ComponentNotEnabled(String::from("mail")))?;
3775- ctx.insert("lists", &mail_cfg.lists.clone());
3776- let body = templates.render("lists.html", &ctx)?;
3777- Ok(Html(body))
3778- }
3779-
3780- #[debug_handler]
3781- pub async fn threads(
3782- Extension(cfg): Extension<Config>,
3783- Path(params): Path<Params>,
3784- Extension(db): Extension<Arc<Database>>,
3785- Extension((templates, mut ctx)): Extension<Template>,
3786- ) -> Result<Html<String>, Error> {
3787- ctx.insert("title", "lists");
3788- ctx.insert("nav_elements", &navigation::global("mail", true));
3789- let mail_cfg = cfg
3790- .mail
3791- .ok_or(Error::ComponentNotEnabled(String::from("mail")))?;
3792- let mailing_list = mail_cfg
3793- .lists
3794- .iter()
3795- .find(|list| list.name == params.list_name)
3796- .ok_or(Error::NotFound(params.list_name.clone()))?;
3797- ctx.insert("list", &mailing_list);
3798- let list_id = ListId(params.list_name.clone(), String::default());
3799- let threads = mail::Database(&db).read_threads(&list_id).await?;
3800- ctx.insert("threads", &threads);
3801- let body = templates.render("threads.html", &ctx)?;
3802- Ok(Html(body))
3803- }
3804-
3805- pub async fn thread(
3806- Extension(cfg): Extension<Config>,
3807- Path(params): Path<Params>,
3808- Extension(db): Extension<Arc<Database>>,
3809- // Extension(highlighter): Extension<Highlighter>,
3810- Extension((templates, mut ctx)): Extension<Template>,
3811- ) -> Result<Html<String>, Error> {
3812- let mail_cfg = cfg
3813- .mail
3814- .ok_or(Error::ComponentNotEnabled(String::from("mail")))?;
3815- let mailing_list = mail_cfg
3816- .lists
3817- .iter()
3818- .find(|list| list.name == params.list_name)
3819- .ok_or(Error::NotFound(params.list_name.clone()))?;
3820- let list_id = ListId(params.list_name.clone(), String::default());
3821- let message_id = params.message_id.clone().unwrap();
3822- let thread = mail::Database(&db)
3823- .read_thread(&list_id, &message_id)
3824- .await?;
3825- if thread.is_empty() {
3826- return Err(Error::NotFound(format!(
3827- "Cannot find thread associated with message: {}",
3828- &message_id
3829- )));
3830- };
3831- ctx.insert("nav_elements", &navigation::global("mail", true));
3832- ctx.insert("list", &mailing_list);
3833- ctx.insert("thread_id", &params.thread_id);
3834- let first_message = thread.first().unwrap();
3835- ctx.insert("first_message", &first_message);
3836- ctx.insert("list_name", &params.list_name);
3837- ctx.insert("thread", &thread);
3838- let body = templates.render("thread.html", &ctx)?;
3839- Ok(Html(body))
3840- }
3841-
3842- pub async fn message(
3843- Path(params): Path<Params>,
3844- // Extension(highlighter): Extension<Highlighter>,
3845- // Extension((templates, ctx)): Extension<Template>,
3846- ) -> Result<Html<String>, Error> {
3847- Err(Error::NotFound(format!(
3848- "no mailing list with name: {} exists",
3849- params.list_name
3850- )))
3851- }
3852-
3853- pub async fn export(Path(params): Path<Params>) -> Result<Response, Error> {
3854- Err(Error::NotFound(format!(
3855- "no mailing list with name: {} exists",
3856- params.list_name
3857- )))
3858- }
3859 diff --git a/ayllu/src/web2/routes/mod.rs b/ayllu/src/web2/routes/mod.rs
3860index 2cbf013..f2dca6f 100644
3861--- a/ayllu/src/web2/routes/mod.rs
3862+++ b/ayllu/src/web2/routes/mod.rs
3863 @@ -1,20 +1,14 @@
3864 pub mod about;
3865 pub mod assets;
3866- // pub mod authors;
3867- pub mod blame;
3868 pub mod blob;
3869- pub mod builds;
3870- // pub mod chart;
3871 pub mod commit;
3872 pub mod config;
3873 pub mod finger;
3874 pub mod git;
3875 pub mod index;
3876 pub mod log;
3877- // pub mod mail;
3878 pub mod refs;
3879 pub mod repo;
3880 pub mod rest;
3881 pub mod robots;
3882 pub mod rss;
3883- pub mod xmpp;
3884 diff --git a/ayllu/src/web2/routes/refs.rs b/ayllu/src/web2/routes/refs.rs
3885index b413ce4..c45aa95 100644
3886--- a/ayllu/src/web2/routes/refs.rs
3887+++ b/ayllu/src/web2/routes/refs.rs
3888 @@ -1,3 +1,4 @@
3889+ use askama::Template;
3890 use axum::{
3891 body::Body,
3892 extract::{Extension, Path},
3893 @@ -6,50 +7,75 @@ use axum::{
3894 };
3895 use tokio_util::io::ReaderStream;
3896
3897- use crate::web2::error::Error;
3898- use crate::web2::middleware::repository::Preamble;
3899- use crate::web2::middleware::template::Template;
3900 use crate::web2::navigation;
3901- use ayllu_git::Wrapper;
3902+ use crate::web2::template::filters;
3903+ use crate::web2::{error::Error, template::Base};
3904+ use crate::{web2::middleware::repository::Preamble, with_preamble};
3905+ use ayllu_git::{Branch, Note, Tag, Wrapper};
3906
3907 const INVALID_EXTENSION_MESSAGE: &str = "archive requests must have a .tar.gz extension";
3908
3909 // TODO: Add a branch summary page that shows it's distinction from main
3910
3911+ #[derive(Template)]
3912+ #[template(path = "refs.html")]
3913+ struct RefsPageTemplate<'a> {
3914+ pub base: Base,
3915+ pub collection: &'a str,
3916+ pub name: &'a str,
3917+ pub branches: &'a [Branch],
3918+ pub tags: &'a [Tag],
3919+ pub notes: &'a [Note],
3920+ }
3921+
3922 pub async fn refs(
3923 Extension(preamble): Extension<Preamble>,
3924- Extension((templates, mut ctx)): Extension<Template>,
3925+ Extension(mut base): Extension<Base>,
3926 ) -> Result<Html<String>, Error> {
3927 let repository = Wrapper::new(preamble.repo_path.as_path())?;
3928- ctx.insert(
3929- "nav_elements",
3930- &navigation::primary("refs", &preamble.collection_name, &preamble.repo_name),
3931- );
3932- let branches = repository.branches()?;
3933- ctx.insert("branches", &branches);
3934- let tags = repository.tags()?;
3935- ctx.insert("tags", &tags);
3936- let notes = repository.notes()?;
3937- ctx.insert("notes", &notes);
3938- let body = templates.render("refs.html", &ctx)?;
3939- Ok(Html(body))
3940+ base.nav_elements = navigation::primary("refs", &preamble.collection_name, &preamble.repo_name);
3941+ with_preamble!(base, preamble);
3942+ Ok(Html(
3943+ RefsPageTemplate {
3944+ base,
3945+ collection: &preamble.collection_name,
3946+ name: &preamble.repo_name,
3947+ branches: repository.branches()?.as_slice(),
3948+ tags: repository.tags()?.as_slice(),
3949+ notes: repository.notes()?.as_slice(),
3950+ }
3951+ .render()?,
3952+ ))
3953+ }
3954+
3955+ #[derive(Template)]
3956+ #[template(path = "tag.html")]
3957+ struct TagTemplate {
3958+ pub base: Base,
3959+ pub collection: String,
3960+ pub name: String,
3961+ pub tag: Tag,
3962 }
3963
3964 pub async fn tag(
3965 Extension(preamble): Extension<Preamble>,
3966 Path((_, _, tag_name)): Path<(String, String, String)>,
3967- Extension((templates, mut ctx)): Extension<Template>,
3968+ Extension(mut base): Extension<Base>,
3969 ) -> Result<Html<String>, Error> {
3970 let repository = Wrapper::new(preamble.repo_path.as_path())?;
3971 if let Some(tag) = repository.tags()?.iter().find(|tag| tag.name == tag_name) {
3972- ctx.insert("title", &preamble.repo_name);
3973- ctx.insert("tag", &tag);
3974- ctx.insert(
3975- "nav_elements",
3976- &navigation::primary("refs", &preamble.collection_name, &preamble.repo_name),
3977- );
3978- let body = templates.render("tag.html", &ctx)?;
3979- Ok(Html(body))
3980+ base.nav_elements =
3981+ navigation::primary("refs", &preamble.collection_name, &preamble.repo_name);
3982+ with_preamble!(base, preamble);
3983+ Ok(Html(
3984+ TagTemplate {
3985+ base,
3986+ collection: preamble.collection_name.clone(),
3987+ name: preamble.repo_name.clone(),
3988+ tag: tag.clone(),
3989+ }
3990+ .render()?,
3991+ ))
3992 } else {
3993 Err(Error::NotFound(format!("Tag: {} Not Found", tag_name)))
3994 }
3995 diff --git a/ayllu/src/web2/routes/repo.rs b/ayllu/src/web2/routes/repo.rs
3996index ba0e451..7be1c02 100644
3997--- a/ayllu/src/web2/routes/repo.rs
3998+++ b/ayllu/src/web2/routes/repo.rs
3999 @@ -1,23 +1,26 @@
4000 use std::path::PathBuf;
4001
4002+ use askama::Template;
4003 use axum::{
4004 debug_handler,
4005 extract::{Extension, OriginalUri},
4006 response::Html,
4007 };
4008 use serde::Serialize;
4009- use tera::{to_value, Filter};
4010
4011- use crate::config::Config;
4012+ use crate::{
4013+ config::Config,
4014+ web2::template::{Base, DEFAULT_ANCHOR_SYMBOL},
4015+ with_preamble,
4016+ };
4017
4018 use crate::highlight::TreeSitterAdapter;
4019 use crate::license;
4020 use crate::readme::Renderer;
4021 use crate::web2::error::Error;
4022 use crate::web2::middleware::repository::Preamble;
4023- use crate::web2::middleware::template::Template;
4024 use crate::web2::navigation;
4025- use crate::web2::terautil::Theme;
4026+ use crate::web2::template::filters;
4027 use crate::web2::util;
4028
4029 use ayllu_git::{Commit, Config as GitConfig, TreeEntry, Wrapper};
4030 @@ -31,41 +34,6 @@ const README_FILES: [&str; 6] = [
4031 "README.txt",
4032 ];
4033
4034- struct ItemBuilder {
4035- base_url: PathBuf,
4036- file_path: Option<PathBuf>,
4037- commitish: String,
4038- }
4039-
4040- impl Filter for ItemBuilder {
4041- fn filter(
4042- &self,
4043- value: &serde_json::Value,
4044- args: &std::collections::HashMap<String, serde_json::Value>,
4045- ) -> tera::Result<serde_json::Value> {
4046- // TODO: should clean this up somehow
4047- let kind = args.get("kind").unwrap().as_str().unwrap();
4048- let kind = kind.to_lowercase();
4049- let kind = if kind == "pointer" || kind == "submodule" {
4050- "blob".to_string()
4051- } else {
4052- kind.clone()
4053- };
4054- let mut url = self.base_url.clone();
4055- url.push(kind.clone());
4056- url.push(self.commitish.clone());
4057- if let Some(fp) = &self.file_path {
4058- let fp = fp.to_str().unwrap();
4059- let fp = fp.trim_start_matches('/');
4060- url.push(fp)
4061- }
4062- let value = value.as_str().unwrap();
4063- url.push(value);
4064- let url = url.to_str().unwrap().to_string();
4065- Ok(to_value(url).unwrap())
4066- }
4067- }
4068-
4069 #[derive(Serialize, Clone)]
4070 pub struct ChatLink {
4071 pub kind: String,
4072 @@ -96,6 +64,31 @@ pub struct EmailLink {
4073 pub description: Option<String>,
4074 }
4075
4076+ #[derive(askama::Template)]
4077+ #[template(path = "repo.html")]
4078+ struct PageTemplate<'a> {
4079+ pub base: Base,
4080+ pub collection: &'a str,
4081+ pub name: &'a str,
4082+ pub latest_commit: Option<&'a Commit>,
4083+ pub tree: &'a [(&'a ayllu_git::TreeEntry, &'a ayllu_git::Commit)],
4084+ pub chats: Vec<ChatLink>,
4085+ pub license: Option<&'a str>,
4086+ pub rss_link_all: &'a str,
4087+ pub rss_link_1d: &'a str,
4088+ pub rss_link_1w: &'a str,
4089+ pub rss_link_1m: &'a str,
4090+ pub http_clone_url: &'a str,
4091+ pub git_clone_url: Option<&'a str>,
4092+ pub refname: &'a str,
4093+ pub readme: Option<&'a str>,
4094+ pub rendered_file_name: Option<&'a str>,
4095+ pub rendered_file_size: Option<i64>,
4096+ pub file_path: &'a str,
4097+ pub homepage: Option<&'a str>,
4098+ pub description: Option<&'a str>,
4099+ }
4100+
4101 #[debug_handler]
4102 #[allow(clippy::too_many_arguments)]
4103 pub async fn serve(
4104 @@ -103,18 +96,14 @@ pub async fn serve(
4105 Extension(cfg): Extension<Config>,
4106 Extension(preamble): Extension<Preamble>,
4107 Extension(adapter): Extension<TreeSitterAdapter>,
4108- Extension(current_theme): Extension<Theme>,
4109- Extension((mut templates, mut ctx)): Extension<Template>,
4110+ Extension(mut base): Extension<Base>,
4111 ) -> Result<Html<String>, Error> {
4112 let repository = Wrapper::new(preamble.repo_path.as_path())?;
4113-
4114 let license = license::detect(&repository, preamble.latest_commit_id.clone())?;
4115-
4116 let tree = repository.tree(
4117 preamble.file_path.clone(),
4118 preamble.latest_commit_id.clone(),
4119 )?;
4120-
4121 let recent_commits = repository.resolve_tree(
4122 preamble.file_path.clone(),
4123 &tree,
4124 @@ -123,13 +112,10 @@ pub async fn serve(
4125
4126 let entries_with_commits: Vec<(&TreeEntry, &Commit)> =
4127 tree.iter().zip(recent_commits.iter()).collect();
4128-
4129 // attempt to render readme if a suitable file is present
4130-
4131 let mut readme: Option<String> = None;
4132-
4133 let mut rendered_file_name: Option<String> = None;
4134-
4135+ let mut rendered_file_size: Option<usize> = None;
4136 for path in README_FILES {
4137 let mut readme_path = preamble.file_path.clone().unwrap_or(PathBuf::new());
4138 readme_path.push(path);
4139 @@ -137,127 +123,84 @@ pub async fn serve(
4140 repository.read_string(&readme_path, preamble.latest_commit_id.clone())?
4141 {
4142 rendered_file_name = Some(path.to_string());
4143+ rendered_file_size = Some(content.len());
4144 readme = Some(content);
4145 break;
4146 }
4147 }
4148-
4149- let chats = chat_links(&preamble.config);
4150-
4151- ctx.insert("chats", &chats);
4152-
4153 let readme = match readme {
4154 Some(readme) => {
4155- // find the anchor symbol in the current theme
4156- let anchor_symbol = current_theme
4157- .1
4158- .0
4159- .get("anchor.svg")
4160- .expect("missing default asset");
4161-
4162- let anchor_symbol = anchor_symbol.read_string().await?;
4163 let renderer = Renderer {
4164 origin: &cfg.origin,
4165 collection: &preamble.collection_name,
4166 name: &preamble.repo_name,
4167 refname: &preamble.refname,
4168- anchor_symbol: &anchor_symbol,
4169+ anchor_symbol: DEFAULT_ANCHOR_SYMBOL,
4170 allow_unsafe_markdown: cfg.web.unsafe_markdown,
4171 };
4172- renderer.render(adapter, &readme)
4173+ Some(renderer.render(adapter, &readme))
4174 }
4175- None => String::new(),
4176+ None => None,
4177 };
4178
4179- // Merge everything together and render
4180- templates.register_filter(
4181- "make_url",
4182- ItemBuilder {
4183- base_url: PathBuf::from(format!(
4184- "/{}/{}",
4185- preamble.collection_name, preamble.repo_name
4186- )),
4187- file_path: preamble.file_path.clone(),
4188- commitish: preamble.refname.clone(),
4189- },
4190- );
4191-
4192- ctx.insert("flex_components", &(true));
4193-
4194- ctx.insert("title", &preamble.repo_name);
4195- ctx.insert("sites_url", &preamble.config.homepage);
4196- ctx.insert("readme", &readme);
4197- ctx.insert("file_path", &preamble.file_path_string());
4198-
4199- if preamble.file_path.is_none() {
4200- ctx.insert("container_max_width", &("1800px"));
4201- }
4202-
4203- ctx.insert("tree", &entries_with_commits);
4204- ctx.insert("refname", &preamble.refname.clone());
4205- ctx.insert("show_details", &preamble.file_path.is_none());
4206- ctx.insert(
4207- "nav_elements",
4208- &navigation::primary("project", &preamble.collection_name, &preamble.repo_name),
4209- );
4210- ctx.insert("latest_commit", &preamble.latest_commit);
4211- ctx.insert("authors_count", &0);
4212- ctx.insert("rendered_file_name", &rendered_file_name);
4213- ctx.insert(
4214- "rss_link_all",
4215- &util::select_path(&uri, Some(0), None).map(|mut path| {
4216+ let rss_link_all = util::select_path(&uri, Some(0), None)
4217+ .map(|mut path| {
4218 path.push("rss/firehose.xml");
4219- path.to_str().unwrap().to_string()
4220- }),
4221- );
4222- ctx.insert(
4223- "rss_link_1d",
4224- &util::select_path(&uri, Some(0), None).map(|mut path| {
4225+ path.to_string_lossy().to_string()
4226+ })
4227+ .unwrap();
4228+
4229+ let rss_link_1d = util::select_path(&uri, Some(0), None)
4230+ .map(|mut path| {
4231 path.push("rss/1d.xml");
4232- path.to_str().unwrap().to_string()
4233- }),
4234- );
4235- ctx.insert(
4236- "rss_link_1w",
4237- &util::select_path(&uri, Some(0), None).map(|mut path| {
4238+ path.to_string_lossy().to_string()
4239+ })
4240+ .unwrap();
4241+
4242+ let rss_link_1w = util::select_path(&uri, Some(0), None)
4243+ .map(|mut path| {
4244 path.push("rss/1w.xml");
4245- path.to_str().unwrap().to_string()
4246- }),
4247- );
4248- ctx.insert(
4249- "rss_link_1m",
4250- &util::select_path(&uri, Some(0), None).map(|mut path| {
4251+ path.to_string_lossy().to_string()
4252+ })
4253+ .unwrap();
4254+
4255+ let rss_link_1m = util::select_path(&uri, Some(0), None)
4256+ .map(|mut path| {
4257 path.push("rss/1m.xml");
4258- path.to_str().unwrap().to_string()
4259- }),
4260- );
4261- ctx.insert(
4262- "http_clone_url",
4263- &format!(
4264+ path.to_string_lossy().to_string()
4265+ })
4266+ .unwrap();
4267+
4268+ base.nav_elements =
4269+ navigation::primary("project", &preamble.collection_name, &preamble.repo_name);
4270+
4271+ with_preamble!(base, preamble);
4272+
4273+ let index = PageTemplate {
4274+ base,
4275+ collection: &preamble.collection_name,
4276+ name: &preamble.repo_name,
4277+ latest_commit: preamble.latest_commit.as_ref(),
4278+ tree: entries_with_commits.as_slice(),
4279+ chats: chat_links(&preamble.config),
4280+ license: license.as_deref(),
4281+ rss_link_all: &rss_link_all,
4282+ rss_link_1d: &rss_link_1d,
4283+ rss_link_1w: &rss_link_1w,
4284+ rss_link_1m: &rss_link_1m,
4285+ http_clone_url: &format!(
4286 "{}/{}/{}",
4287 cfg.origin, preamble.collection_name, preamble.repo_name
4288 ),
4289- );
4290- let git_clone_url = match cfg.git.clone_url {
4291- Some(base) => {
4292- let clone_url = format!(
4293- "{}:{}/{}.git",
4294- base, preamble.collection_name, preamble.repo_name,
4295- );
4296- Some(clone_url)
4297- }
4298- None => None,
4299- };
4300- ctx.insert("git_clone_url", &git_clone_url);
4301- match license {
4302- Some(license) => {
4303- ctx.insert("license", &license);
4304- }
4305- None => {
4306- ctx.insert("license", &String::new());
4307- }
4308+ git_clone_url: None, // FIXME
4309+ refname: &preamble.refname,
4310+ readme: readme.as_deref(),
4311+ rendered_file_name: rendered_file_name.as_deref(),
4312+ rendered_file_size: rendered_file_size.map(|size| size as i64),
4313+ file_path: &preamble.file_path_string(),
4314+ homepage: preamble.config.homepage.as_deref(),
4315+ description: preamble.config.description.as_deref(),
4316 };
4317
4318- let body = templates.render("repo.html", &ctx)?;
4319- Ok(Html(body))
4320+ Ok(Html(index.render()?))
4321 }
4322 diff --git a/ayllu/src/web2/routes/rss.rs b/ayllu/src/web2/routes/rss.rs
4323index 4a45b70..c394c63 100644
4324--- a/ayllu/src/web2/routes/rss.rs
4325+++ b/ayllu/src/web2/routes/rss.rs
4326 @@ -1,5 +1,6 @@
4327 use std::path::PathBuf;
4328
4329+ use askama::Template;
4330 use axum::{
4331 body::Body,
4332 extract::{Extension, OriginalUri},
4333 @@ -9,13 +10,11 @@ use axum::{
4334 use mime::TEXT_XML;
4335 use rss::{Channel, Guid, Item};
4336 use serde::Serialize;
4337- use tera::{Context, Tera};
4338 use time::{format_description::well_known::Rfc2822, macros::time, Duration, OffsetDateTime};
4339
4340 use crate::config::{Collection, Config};
4341 use crate::web2::error::Error;
4342 use crate::web2::middleware::repository::Preamble;
4343- use crate::web2::middleware::template::Template;
4344 use crate::web2::util;
4345 use ayllu_git::{Commit, Scanner, Stats, Tag, Wrapper};
4346
4347 @@ -93,17 +92,28 @@ impl Timeframe {
4348 }
4349 }
4350
4351+ #[derive(Template)]
4352+ #[template(path = "rss_summary.html")]
4353+ struct Summary<'a> {
4354+ pub origin: &'a str,
4355+ pub start_date: &'a str,
4356+ pub end_date: &'a str,
4357+ pub n_tags: usize,
4358+ pub n_commits: usize,
4359+ pub n_projects: Option<usize>,
4360+ pub entries: Vec<Data>,
4361+ }
4362+
4363 #[derive(Serialize)]
4364 struct Data {
4365 name: String,
4366- tags: Vec<Tag>,
4367- commits: Vec<Commit>,
4368+ n_tags: Option<usize>,
4369+ tags: Option<Vec<Tag>>,
4370+ commits: Option<Vec<Commit>>,
4371 }
4372
4373 /// Channel builder for RSS feeds for on all repositories hosted on the server.
4374 struct Builder {
4375- templates: Tera,
4376- context: Context,
4377 origin: String,
4378 title: String,
4379 time_to_live: Option<Duration>,
4380 @@ -222,8 +232,17 @@ impl Builder {
4381 if !tags.is_empty() || !commits.is_empty() {
4382 all_data.push(Data {
4383 name: repository.name(),
4384- tags,
4385- commits,
4386+ n_tags: if tags.is_empty() {
4387+ None
4388+ } else {
4389+ Some(tags.len())
4390+ },
4391+ tags: if tags.is_empty() { None } else { Some(tags) },
4392+ commits: if commits.is_empty() {
4393+ None
4394+ } else {
4395+ Some(commits)
4396+ },
4397 });
4398 }
4399 }
4400 @@ -233,24 +252,25 @@ impl Builder {
4401 return Ok(self.channel(vec![]));
4402 }
4403
4404- let mut ctx = self.context.clone();
4405-
4406- ctx.insert("origin", &self.origin);
4407- ctx.insert("start_date", &start.date().to_string());
4408- ctx.insert("end_date", &end.date().to_string());
4409-
4410- let n_tags = all_data
4411- .iter()
4412- .fold(0, |accm, entries| accm + entries.tags.len());
4413- ctx.insert("n_tags", &n_tags);
4414- let n_commits = all_data
4415- .iter()
4416- .fold(0, |accm, entries| accm + entries.commits.len());
4417- ctx.insert("n_commits", &n_commits);
4418- ctx.insert("n_projects", &all_data.len());
4419- ctx.insert("entries", &all_data);
4420+ let summary = Summary {
4421+ origin: &self.origin,
4422+ start_date: &start.date().to_string(),
4423+ end_date: &end.date().to_string(),
4424+ n_tags: all_data.iter().fold(0, |accm, entries| {
4425+ accm + entries.tags.as_ref().map_or(0, |tags| tags.len())
4426+ }),
4427+ n_commits: all_data.iter().fold(0, |accm, entries| {
4428+ accm + entries.commits.as_ref().map_or(0, |commits| commits.len())
4429+ }),
4430+ n_projects: if all_data.len() == 0 {
4431+ None
4432+ } else {
4433+ Some(all_data.len())
4434+ },
4435+ entries: all_data,
4436+ };
4437
4438- let description = self.templates.render("rss_summary.html", &ctx).unwrap();
4439+ let description = summary.render().unwrap();
4440 let mut item = rss::Item::default();
4441 item.set_description(Some(description));
4442 item.set_guid(Some(Guid {
4443 @@ -266,13 +286,8 @@ impl Builder {
4444 }
4445 }
4446
4447- pub async fn feed_firehose(
4448- Extension(cfg): Extension<Config>,
4449- Extension((templates, context)): Extension<Template>,
4450- ) -> Result<Response, Error> {
4451+ pub async fn feed_firehose(Extension(cfg): Extension<Config>) -> Result<Response, Error> {
4452 let builder = Builder {
4453- templates,
4454- context,
4455 origin: cfg.origin,
4456 title: String::from("Firehose"),
4457 time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
4458 @@ -289,13 +304,8 @@ pub async fn feed_firehose(
4459 Ok(response)
4460 }
4461
4462- pub async fn feed_1d(
4463- Extension(cfg): Extension<Config>,
4464- Extension((templates, context)): Extension<Template>,
4465- ) -> Result<Response, Error> {
4466+ pub async fn feed_1d(Extension(cfg): Extension<Config>) -> Result<Response, Error> {
4467 let builder = Builder {
4468- templates,
4469- context,
4470 origin: cfg.origin,
4471 title: String::from("Daily Update Summary"),
4472 time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
4473 @@ -312,13 +322,8 @@ pub async fn feed_1d(
4474 Ok(response)
4475 }
4476
4477- pub async fn feed_1w(
4478- Extension(cfg): Extension<Config>,
4479- Extension((templates, context)): Extension<Template>,
4480- ) -> Result<Response, Error> {
4481+ pub async fn feed_1w(Extension(cfg): Extension<Config>) -> Result<Response, Error> {
4482 let builder = Builder {
4483- templates,
4484- context,
4485 origin: cfg.origin,
4486 title: String::from("Weekly Update Summary"),
4487 time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
4488 @@ -335,13 +340,8 @@ pub async fn feed_1w(
4489 Ok(response)
4490 }
4491
4492- pub async fn feed_1m(
4493- Extension(cfg): Extension<Config>,
4494- Extension((templates, context)): Extension<Template>,
4495- ) -> Result<Response, Error> {
4496+ pub async fn feed_1m(Extension(cfg): Extension<Config>) -> Result<Response, Error> {
4497 let builder = Builder {
4498- templates,
4499- context,
4500 origin: cfg.origin,
4501 title: String::from("Monthly Update Summary"),
4502 time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
4503 @@ -362,12 +362,9 @@ pub async fn feed_repository_firehose(
4504 uri: OriginalUri,
4505 Extension(cfg): Extension<Config>,
4506 Extension(preamble): Extension<Preamble>,
4507- Extension((templates, context)): Extension<Template>,
4508 ) -> Result<Response, Error> {
4509 let project_url = util::project_url(&uri, cfg.origin.as_str());
4510 let builder = Builder {
4511- templates,
4512- context,
4513 origin: cfg.origin,
4514 title: format!(
4515 "Firehose for {}/{}",
4516 @@ -388,12 +385,9 @@ pub async fn feed_repository_1d(
4517 uri: OriginalUri,
4518 Extension(cfg): Extension<Config>,
4519 Extension(preamble): Extension<Preamble>,
4520- Extension((templates, context)): Extension<Template>,
4521 ) -> Result<Response, Error> {
4522 let project_url = util::project_url(&uri, cfg.origin.as_str());
4523 let builder = Builder {
4524- templates,
4525- context,
4526 origin: cfg.origin,
4527 title: format!(
4528 "Daily Update Summary for {}/{}",
4529 @@ -414,12 +408,9 @@ pub async fn feed_repository_1w(
4530 uri: OriginalUri,
4531 Extension(cfg): Extension<Config>,
4532 Extension(preamble): Extension<Preamble>,
4533- Extension((templates, context)): Extension<Template>,
4534 ) -> Result<Response, Error> {
4535 let project_url = util::project_url(&uri, cfg.origin.as_str());
4536 let builder = Builder {
4537- templates,
4538- context,
4539 origin: cfg.origin,
4540 title: format!(
4541 "Weekly Update Summary for {}/{}",
4542 @@ -440,12 +431,9 @@ pub async fn feed_repository_1m(
4543 uri: OriginalUri,
4544 Extension(cfg): Extension<Config>,
4545 Extension(preamble): Extension<Preamble>,
4546- Extension((templates, context)): Extension<Template>,
4547 ) -> Result<Response, Error> {
4548 let project_url = util::project_url(&uri, cfg.origin.as_str());
4549 let builder = Builder {
4550- templates,
4551- context,
4552 origin: cfg.origin,
4553 title: format!(
4554 "Monthly Update Summary for {}/{}",
4555 @@ -466,7 +454,6 @@ pub async fn feed_repository_1m(
4556 mod tests {
4557
4558 use super::*;
4559- use tera::Tera;
4560 use time::{format_description::well_known::Rfc2822, macros::datetime, OffsetDateTime};
4561
4562 use ayllu_git::testing;
4563 @@ -497,12 +484,7 @@ mod tests {
4564 .as_str(),
4565 ]);
4566 let (name, path) = test_repo.build().expect("failed to init repo");
4567- let templates =
4568- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4569- let context = Context::new();
4570 let builder = Builder {
4571- templates,
4572- context,
4573 origin: String::from("localhost:8080"),
4574 title: String::from("test"),
4575 time_to_live: Some(Duration::HOUR),
4576 @@ -544,12 +526,7 @@ mod tests {
4577 .as_str(),
4578 ]);
4579 let (name, path) = test_repo.build().expect("failed to init repo");
4580- let templates =
4581- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4582- let context = Context::new();
4583 let builder = Builder {
4584- templates,
4585- context,
4586 origin: String::from("localhost:8080"),
4587 title: String::from("test"),
4588 time_to_live: Some(Duration::HOUR),
4589 @@ -602,12 +579,7 @@ mod tests {
4590 .as_str(),
4591 ]);
4592 let (name, path) = test_repo.build().expect("failed to init repo");
4593- let templates =
4594- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4595- let context = Context::new();
4596 let builder = Builder {
4597- templates,
4598- context,
4599 origin: String::from("localhost:8080"),
4600 title: String::from("test"),
4601 time_to_live: Some(Duration::HOUR),
4602 @@ -640,12 +612,7 @@ mod tests {
4603 .as_str(),
4604 ]);
4605 let (name, path) = test_repo.build().expect("failed to init repo");
4606- let templates =
4607- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4608- let context = Context::new();
4609 let builder = Builder {
4610- templates,
4611- context,
4612 origin: String::from("localhost:8080"),
4613 title: String::from("test"),
4614 time_to_live: Some(Duration::HOUR),
4615 @@ -684,12 +651,7 @@ mod tests {
4616 .as_str(),
4617 ]);
4618 let (name, path) = test_repo.build().expect("failed to init repo");
4619- let templates =
4620- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4621- let context = Context::new();
4622 let builder = Builder {
4623- templates,
4624- context,
4625 origin: String::from("localhost:8080"),
4626 title: String::from("test"),
4627 time_to_live: Some(Duration::HOUR),
4628 @@ -718,12 +680,7 @@ mod tests {
4629 .as_str(),
4630 ]);
4631 let (name, path) = test_repo.build().expect("failed to init repo");
4632- let templates =
4633- Tera::new("themes/default/templates/*.html").expect("failed to load templates");
4634- let context = Context::new();
4635 let builder = Builder {
4636- templates,
4637- context,
4638 origin: String::from("localhost:8080"),
4639 title: String::from("test"),
4640 time_to_live: Some(Duration::HOUR),
4641 diff --git a/ayllu/src/web2/routes/xmpp.rs b/ayllu/src/web2/routes/xmpp.rs
4642deleted file mode 100644
4643index e92db13..0000000
4644--- a/ayllu/src/web2/routes/xmpp.rs
4645+++ /dev/null
4646 @@ -1,67 +0,0 @@
4647- use axum::{
4648- extract::{Extension, Path},
4649- response::Html,
4650- };
4651- use serde::Deserialize;
4652-
4653- use crate::config::Config;
4654- use crate::web2::error::Error;
4655- use crate::web2::middleware::template::Template;
4656- use crate::web2::navigation;
4657-
4658- // #[derive(Debug, Serialize)]
4659- // struct ChannelWithStats {
4660- // pub name: String,
4661- // pub n_users: i64,
4662- // pub n_messages: i64,
4663- // pub online: bool,
4664- // }
4665- //
4666- // #[derive(Debug, Serialize)]
4667- // struct XmppMessage {
4668- // pub id: String,
4669- // pub nickname: String,
4670- // pub timestamp: String,
4671- // pub body: String,
4672- // }
4673- pub async fn channels(
4674- Extension(_cfg): Extension<Config>,
4675- Extension((templates, mut ctx)): Extension<Template>,
4676- ) -> Result<Html<String>, Error> {
4677- ctx.insert("title", "Discussions");
4678- ctx.insert("nav_elements", &navigation::global("xmpp", true));
4679- // let client = initiator.xmpp.unwrap();
4680- // // FIXME
4681- // let channels = client.stats(context::current(), vec![]).await??;
4682- // ctx.insert("channels", &channels);
4683- let body = templates.render("channels.html", &ctx)?;
4684- Ok(Html(body))
4685- }
4686-
4687- #[derive(Deserialize)]
4688- pub struct ChannelParams {
4689- pub channel: String,
4690- // pub last_message: Option<String>,
4691- }
4692-
4693- pub async fn channel(
4694- Path(params): Path<ChannelParams>,
4695- Extension(_cfg): Extension<Config>,
4696- Extension((templates, mut ctx)): Extension<Template>,
4697- ) -> Result<Html<String>, Error> {
4698- ctx.insert("title", "lists");
4699- ctx.insert("nav_elements", &navigation::global("xmpp", true));
4700- ctx.insert("channel", &params.channel);
4701- // let messages = client
4702- // .messages(
4703- // context::current(),
4704- // params.channel,
4705- // params.last_message,
4706- // 1000,
4707- // )
4708- // .await??;
4709- // ctx.insert("messages", &messages);
4710- // ctx.insert("lists", &cfg.mail.unwrap().lists);
4711- let body = templates.render("channel.html", &ctx)?;
4712- Ok(Html(body))
4713- }
4714 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
4715index e3c0b59..39ee6b9 100644
4716--- a/ayllu/src/web2/server.rs
4717+++ b/ayllu/src/web2/server.rs
4718 @@ -15,19 +15,14 @@ use tower_http::{
4719 };
4720 use tracing::{Level, Span};
4721
4722- use crate::config::Config;
4723 use crate::highlight::{Highlighter, Loader, TreeSitterAdapter};
4724 use crate::languages::{Hint, LANGUAGE_TABLE};
4725 use crate::web2::middleware::error;
4726 use crate::web2::middleware::repository;
4727 use crate::web2::middleware::sites;
4728- use crate::web2::middleware::template;
4729- use crate::web2::middleware::theme;
4730 use crate::web2::routes::about;
4731 use crate::web2::routes::assets;
4732- use crate::web2::routes::blame;
4733 use crate::web2::routes::blob;
4734- use crate::web2::routes::builds;
4735 use crate::web2::routes::commit;
4736 use crate::web2::routes::config;
4737 use crate::web2::routes::finger;
4738 @@ -40,8 +35,7 @@ use crate::web2::routes::rest::discovery;
4739 use crate::web2::routes::rest::ping;
4740 use crate::web2::routes::robots;
4741 use crate::web2::routes::rss;
4742- use crate::web2::routes::xmpp;
4743- use crate::web2::terautil::load_themes;
4744+ use crate::{config::Config, web2::middleware::template};
4745
4746 pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4747 let keywords = match &cfg.tree_sitter {
4748 @@ -79,15 +73,6 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4749 let highlighter = Highlighter::new(&keywords);
4750 let adapter = TreeSitterAdapter(highlighter.clone());
4751
4752- let themes = load_themes(cfg.web.themes.as_ref().map_or(vec![], |themes| {
4753- themes
4754- .iter()
4755- .map(|theme| (theme.name.clone(), theme.path.clone()))
4756- .collect()
4757- }))?;
4758-
4759- let theme_names: Vec<String> = themes.0.iter().map(|theme| theme.0.clone()).collect();
4760-
4761 let site_mapping = if cfg.sites.enabled {
4762 sites::sites(cfg.clone())?
4763 } else {
4764 @@ -109,12 +94,7 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4765 .route("/rss/1w.xml", routing::get(rss::feed_1w))
4766 .route("/rss/1m.xml", routing::get(rss::feed_1m))
4767 .route("/about", routing::get(about::serve))
4768- .route("/config", routing::get(config::serve).post(config::update))
4769- .layer(Extension(theme_names))
4770- .layer(from_fn_with_state(
4771- Arc::new((cfg.clone(), themes.clone())),
4772- template::middleware,
4773- )),
4774+ .route("/config", routing::get(config::serve).post(config::update)),
4775 )
4776 .nest(
4777 "/0",
4778 @@ -122,38 +102,6 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4779 .route("/index", routing::get(discovery::serve))
4780 .route("/ping", routing::get(ping::serve)),
4781 )
4782- // .nest(
4783- // "/mail",
4784- // Router::new()
4785- // .route("/", routing::get(mail::lists))
4786- // .route("/:list_name", routing::get(mail::threads))
4787- // .route("/export/:list_name", routing::get(mail::export))
4788- // .route("/export/:list_name/:thread_id", routing::get(mail::export))
4789- // .route(
4790- // "/export/:list_name/:thread_id/:message_id",
4791- // routing::get(mail::export),
4792- // )
4793- // .route("/thread/:list_name/:message_id", routing::get(mail::thread))
4794- // .route(
4795- // "/message/:list_name/:message_id",
4796- // routing::get(mail::message),
4797- // )
4798- // .layer(from_fn_with_state(
4799- // Arc::new((cfg.clone(), themes.clone())),
4800- // template::middleware,
4801- // )),
4802- // )
4803- .nest(
4804- "/xmpp",
4805- Router::new()
4806- .route("/", routing::get(xmpp::channels))
4807- .route("/:channel", routing::get(xmpp::channel))
4808- .route("/:channel/:last_message", routing::get(xmpp::channel))
4809- .layer(from_fn_with_state(
4810- Arc::new((cfg.clone(), themes.clone())),
4811- template::middleware,
4812- )),
4813- )
4814 .nest(
4815 "/:collection/:name",
4816 Router::new()
4817 @@ -171,24 +119,8 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4818 .route("/log", routing::get(log_route::serve))
4819 .route("/log/:commitish", routing::get(log_route::serve))
4820 .route("/log/:commitish/*file_path", routing::get(log_route::serve))
4821- // .route("/charts", routing::get(chart::serve))
4822- // .route("/chart/:kind", routing::get(chart::serve))
4823- // .route("/chart/:kind/:commit_id", routing::get(chart::serve))
4824- // .route(
4825- // "/:kind/:commit_id/:width/:height",
4826- // routing::get(chart::serve),
4827- // )
4828- // .route("/authors", routing::get(authors::serve))
4829- .route("/blame/:commitish/*file_path", routing::get(blame::serve))
4830 .route("/blob/:commitish/*file_path", routing::get(blob::serve))
4831 .route("/raw/:commitish/*file_path", routing::get(blob::serve_raw))
4832- .route("/builds", routing::get(builds::serve))
4833- .route("/builds/badge/:commitish", routing::get(builds::badge))
4834- .route("/builds/:commit_id", routing::get(builds::details))
4835- .route(
4836- "/builds/:commit_id/:build_id",
4837- routing::get(builds::details),
4838- )
4839 .route("/refs", routing::get(refs::refs))
4840 .route("/refs/tag/:tag_id", routing::get(refs::tag))
4841 .route("/refs/archive/:ref_id", routing::get(refs::archive))
4842 @@ -201,15 +133,7 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4843 "/git-upload-pack",
4844 routing::on(MethodFilter::GET.or(MethodFilter::POST), git::handle),
4845 )
4846- .layer(from_fn_with_state(cfg.clone(), repository::middleware))
4847- .layer(from_fn_with_state(
4848- Arc::new((cfg.clone(), themes.clone())),
4849- template::middleware,
4850- ))
4851- .layer(from_fn_with_state(
4852- Arc::new((cfg.clone(), themes.clone())),
4853- theme::middleware,
4854- )),
4855+ .layer(from_fn_with_state(cfg.clone(), repository::middleware)),
4856 )
4857 .route(
4858 "/.well-known/webfinger",
4859 @@ -219,18 +143,17 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
4860 "/static",
4861 Router::new()
4862 .route("/main.min.css", routing::get(assets::serve_stylesheet))
4863- .route("/:name", routing::get(assets::serve_asset))
4864- .with_state(Arc::new((cfg.clone(), themes.clone()))),
4865+ .route("/logo.svg", routing::get(assets::serve_logo))
4866+ .route("/feed.svg", routing::get(assets::serve_feed_icon))
4867+ .route("/feed.xsl", routing::get(assets::serve_rss_xslt))
4868+ .with_state(cfg.clone()),
4869 )
4870 .layer(Extension(cfg.clone()))
4871- // .layer(Extension(Arc::new(db)))
4872 .layer(Extension(highlighter))
4873 .layer(Extension(adapter))
4874+ .layer(from_fn_with_state(cfg.clone(), template::middleware))
4875 // error handling
4876- .layer(from_fn_with_state(
4877- Arc::new((cfg.clone(), themes.clone())),
4878- error::middleware,
4879- ))
4880+ .layer(from_fn_with_state(Arc::new(cfg.clone()), error::middleware))
4881 // git hosted static sites
4882 .layer(from_fn_with_state(
4883 (cfg.clone(), site_mapping),
4884 diff --git a/ayllu/src/web2/template.rs b/ayllu/src/web2/template.rs
4885new file mode 100644
4886index 0000000..0406a5c
4887--- /dev/null
4888+++ b/ayllu/src/web2/template.rs
4889 @@ -0,0 +1,118 @@
4890+ use serde::{Deserialize, Serialize};
4891+
4892+ pub type Items = Vec<(String, String, bool)>;
4893+
4894+ // TODO: Make configurable
4895+ pub const DEFAULT_LOGO: &str = include_str!("../../assets/logo.svg");
4896+ pub const DEFAULT_RSS_ICON: &str = include_str!("../../assets/feed.svg");
4897+ pub const DEFAULT_ANCHOR_SYMBOL: &str = include_str!("../../assets/anchor.svg");
4898+ pub const DEFAULT_THEME_CSS: &str = include_str!("../../themes/base.css");
4899+ pub const DEFAULT_RSS_XSLT_FILE: &str = include_str!("../../assets/feed.xsl");
4900+
4901+ // base.render_time = Some(
4902+ // preamble
4903+ // .start_time
4904+ // .elapsed()
4905+ // .unwrap()
4906+ // .as_millis()
4907+ // .to_string(),
4908+ // );
4909+ // base.current_time = timeutil::timestamp();
4910+
4911+ #[macro_export]
4912+ macro_rules! with_preamble {
4913+ ($base:expr, $preamble:expr) => {{
4914+ let now = timeutil::timestamp();
4915+ $base.current_time = now;
4916+ $base.render_time = Some(
4917+ $preamble
4918+ .start_time
4919+ .elapsed()
4920+ .unwrap()
4921+ .as_millis()
4922+ .to_string(),
4923+ );
4924+ }};
4925+ }
4926+
4927+ /// top-level theme options available in all pages
4928+ #[derive(Clone, Serialize, Deserialize, Debug, Default)]
4929+ pub struct Base {
4930+ pub title: String,
4931+ pub origin: String,
4932+ pub site_name: String,
4933+ pub collection: Option<String>,
4934+ pub name: Option<String>,
4935+ pub url: String,
4936+ pub path: String,
4937+ pub fluid: bool,
4938+ pub keywords: String,
4939+ pub description: String,
4940+ pub subpath_mode: bool,
4941+ pub render_time: Option<String>,
4942+ pub current_time: String,
4943+ pub nav_elements: Items,
4944+ pub logo: String,
4945+ pub feed_icon: String,
4946+ }
4947+
4948+ impl Base {
4949+ pub fn with_nav(&mut self, items: &[(&str, &str, bool)]) {
4950+ self.nav_elements = Vec::from_iter(
4951+ items
4952+ .iter()
4953+ .map(|(name, link, active)| (name.to_string(), link.to_string(), *active)),
4954+ );
4955+ }
4956+ }
4957+
4958+ pub mod filters {
4959+
4960+ use file_mode::Mode;
4961+ use time::{format_description::well_known, OffsetDateTime};
4962+
4963+ use crate::web2::util;
4964+
4965+ pub fn friendly_time(epoch: &i64, _: &dyn askama::Values) -> askama::Result<String> {
4966+ Ok(timeutil::friendly(*epoch as u64))
4967+ }
4968+
4969+ pub fn format_epoch(epoch: &i64, _: &dyn askama::Values) -> askama::Result<String> {
4970+ let ts = OffsetDateTime::from_unix_timestamp(*epoch).unwrap();
4971+ let formatted = ts.format(&well_known::Rfc2822).unwrap();
4972+ Ok(formatted)
4973+ }
4974+
4975+ pub fn file_mode(mode: &i32, _: &dyn askama::Values) -> askama::Result<String> {
4976+ let mode = Mode::new((*mode) as u32, 0o777);
4977+ Ok(mode.to_string())
4978+ }
4979+
4980+ pub fn human_bytes(bytes: &f64, _: &dyn askama::Values) -> askama::Result<String> {
4981+ Ok(util::human_bytes(*bytes))
4982+ }
4983+
4984+ pub fn human_bytes_i64(bytes: &i64, _: &dyn askama::Values) -> askama::Result<String> {
4985+ Ok(util::human_bytes(*bytes as f64))
4986+ }
4987+
4988+ pub fn make_object_url(
4989+ filename: &str,
4990+ _: &dyn askama::Values,
4991+ kind: &ayllu_git::Kind,
4992+ collection: &str,
4993+ name: &str,
4994+ commitish: &str,
4995+ file_path: &str,
4996+ ) -> askama::Result<String> {
4997+ let buf = std::path::PathBuf::from_iter(&[
4998+ collection,
4999+ name,
5000+ &kind.to_string(),
5001+ commitish,
5002+ file_path,
5003+ filename,
5004+ ]);
5005+ Ok(format!("/{}", buf.to_string_lossy()))
5006+ }
5007+ }
5008 diff --git a/ayllu/src/web2/terautil/filters.rs b/ayllu/src/web2/terautil/filters.rs
5009deleted file mode 100644
5010index 4aa2288..0000000
5011--- a/ayllu/src/web2/terautil/filters.rs
5012+++ /dev/null
5013 @@ -1,78 +0,0 @@
5014- use std::fs::read_to_string;
5015-
5016- use tera::{to_value, Filter, Result, Value};
5017-
5018- use file_mode::Mode;
5019- use time::{format_description::well_known, OffsetDateTime};
5020-
5021- use crate::time as ctime;
5022- use crate::web2::terautil::themes::Assets;
5023- use crate::web2::util;
5024-
5025- pub struct FriendlyTime {}
5026-
5027- impl Filter for FriendlyTime {
5028- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
5029- let ts = ctime::friendly(value.as_u64().unwrap());
5030- let result = to_value(ts)?;
5031- Ok(result)
5032- }
5033- }
5034-
5035- pub struct FormatEpoch {}
5036-
5037- impl Filter for FormatEpoch {
5038- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
5039- let ts = OffsetDateTime::from_unix_timestamp(value.as_i64().unwrap()).unwrap();
5040- let formatted = ts.format(&well_known::Rfc2822).unwrap();
5041- let result = to_value(formatted)?;
5042- Ok(result)
5043- }
5044- }
5045-
5046- pub struct FileMode {}
5047-
5048- impl Filter for FileMode {
5049- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
5050- let filemode = value.as_u64().unwrap() as u32;
5051- let mode = Mode::new(filemode, 0o777);
5052- let result = to_value(mode.to_string())?;
5053- Ok(result)
5054- }
5055- }
5056-
5057- pub struct HumanBytes {}
5058-
5059- impl Filter for HumanBytes {
5060- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
5061- let bytes = value.as_f64().unwrap();
5062- let result = to_value(util::human_bytes(bytes))?;
5063- Ok(result)
5064- }
5065- }
5066-
5067- pub struct Emojis(pub Assets);
5068-
5069- impl Filter for Emojis {
5070- fn filter(&self, value: &Value, _: &std::collections::HashMap<String, Value>) -> Result<Value> {
5071- let name = value.as_str().unwrap();
5072- if let Some(asset) = self.0 .0.get(name) {
5073- match asset {
5074- super::themes::Asset::Raw(content) => {
5075- let content = String::from_utf8_lossy(content);
5076- return Ok(to_value(content)?);
5077- }
5078- super::themes::Asset::FilePath(path) => {
5079- let file_name = path.file_name().unwrap().to_string_lossy();
5080- if file_name == name {
5081- if let Ok(content) = read_to_string(path) {
5082- return Ok(to_value(content)?);
5083- }
5084- }
5085- }
5086- }
5087- };
5088- tracing::warn!("failed to load theme asset: {}", name);
5089- Ok(to_value("")?)
5090- }
5091- }
5092 diff --git a/ayllu/src/web2/terautil/loader.rs b/ayllu/src/web2/terautil/loader.rs
5093deleted file mode 100644
5094index 635076f..0000000
5095--- a/ayllu/src/web2/terautil/loader.rs
5096+++ /dev/null
5097 @@ -1,68 +0,0 @@
5098- use serde::{Deserialize, Serialize};
5099- use tera::{Context, Tera};
5100-
5101- use super::Themes;
5102-
5103- const DEFAULT_NAV: &[(&str, &str)] = &[];
5104-
5105- /// top-level theme options available in all pages
5106- #[derive(Serialize, Deserialize, Debug, Default)]
5107- pub struct Options {
5108- pub origin: String,
5109- pub site_name: String,
5110- pub collection: Option<String>,
5111- pub name: Option<String>,
5112- pub url: String,
5113- pub path: String,
5114- pub fluid: bool,
5115- pub subpath_mode: bool,
5116- }
5117-
5118- pub struct Loader {
5119- pub themes: Themes,
5120- pub default_theme: String,
5121- }
5122-
5123- impl Loader {
5124- pub fn theme(&self, name: Option<&str>) -> super::themes::Theme {
5125- let default_tmpl = || {
5126- self.themes
5127- .0
5128- .get(&self.default_theme)
5129- .expect("default theme is not configured")
5130- .clone()
5131- };
5132-
5133- match name {
5134- Some(name) => match self.themes.0.get(name) {
5135- Some(theme) => theme.clone(),
5136- None => default_tmpl(),
5137- },
5138- None => default_tmpl(),
5139- }
5140- }
5141-
5142- /// load the tera context of the theme or default theme if unspecified
5143- pub fn load(&self, options: Options, theme_name: Option<String>) -> (Tera, Context) {
5144- let theme = self.theme(theme_name.as_deref());
5145- let mut ctx = Context::new();
5146- ctx.insert("title", "");
5147- ctx.insert("origin", &options.origin);
5148- ctx.insert("collection", &options.collection);
5149- ctx.insert("name", &options.name);
5150- ctx.insert("site_name", &options.site_name.clone());
5151- ctx.insert("nav_elements", DEFAULT_NAV);
5152- // let url = req.uri();
5153- ctx.insert("url", &options.url);
5154- ctx.insert("path", &options.path);
5155- ctx.insert("fluid", &false);
5156- ctx.insert("subpath_mode", &options.subpath_mode);
5157- ctx.insert("current_time", &time::OffsetDateTime::now_utc().to_string());
5158- ctx.insert("container_max_width", "1200px");
5159- // TODO: Make configurable
5160- ctx.insert("description", "Ayllu Code Forge");
5161- // TODO: Make configurable
5162- ctx.insert("keywords", "code, forge, free, software");
5163- (theme.0, ctx)
5164- }
5165- }
5166 diff --git a/ayllu/src/web2/terautil/mod.rs b/ayllu/src/web2/terautil/mod.rs
5167deleted file mode 100644
5168index 5d8b6e9..0000000
5169--- a/ayllu/src/web2/terautil/mod.rs
5170+++ /dev/null
5171 @@ -1,6 +0,0 @@
5172- mod filters;
5173- mod loader;
5174- mod themes;
5175-
5176- pub use loader::*;
5177- pub use themes::*;
5178 diff --git a/ayllu/src/web2/terautil/themes.rs b/ayllu/src/web2/terautil/themes.rs
5179deleted file mode 100644
5180index 8b2bb5f..0000000
5181--- a/ayllu/src/web2/terautil/themes.rs
5182+++ /dev/null
5183 @@ -1,193 +0,0 @@
5184- use std::collections::BTreeMap;
5185- use std::fs::metadata;
5186- use std::io::Error as IoError;
5187- use std::path::PathBuf;
5188-
5189- use globwalk::{glob_builder, DirEntry, FileType, GlobError, WalkError};
5190- use include_dir::{include_dir, Dir};
5191- use tera::{Error as TeraError, Tera};
5192-
5193- use crate::web2::terautil::filters;
5194-
5195- /// An error occured while initializing themes
5196- #[derive(thiserror::Error, Debug)]
5197- pub enum Error {
5198- #[error("Theme Related IO Error: {0}")]
5199- Io(#[from] IoError),
5200- #[error("Template Error: {0}")]
5201- Config(#[from] TeraError),
5202- #[error("Theme Filepath Error: {0}")]
5203- Glob(#[from] GlobError),
5204- #[error("Problem Loading Theme: {0}")]
5205- Walk(#[from] WalkError),
5206- }
5207-
5208- // Ayllu comes with a collection of builtin themes
5209- static BUILTIN_THEMES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/themes");
5210-
5211- static BASE_CSS_TEMPLATE: &str =
5212- include_str!(concat!(env!("OUT_DIR"), "/themes/default/theme.min.css"));
5213-
5214- /// An asset file that may either be in-memory or on the file system, all
5215- /// assets are initialized at start time and dynamic loading of files from the
5216- /// os is unsupported.
5217- #[derive(Clone, Debug)]
5218- pub enum Asset {
5219- /// Path offset from /static and the actual file contents loaded in memory
5220- Raw(Vec<u8>),
5221- /// Path offset from /static and a full filesystem path loaded at startup
5222- FilePath(PathBuf),
5223- }
5224-
5225- impl Asset {
5226- /// read the asset to byte vec
5227- pub async fn read_bytes(&self) -> Result<Vec<u8>, IoError> {
5228- match self {
5229- Asset::Raw(content) => Ok(content.to_vec()),
5230- Asset::FilePath(fp) => tokio::fs::read(fp).await,
5231- }
5232- }
5233-
5234- /// read the asset as a string assuming utf8
5235- pub async fn read_string(&self) -> Result<String, IoError> {
5236- let raw_bytes = self.read_bytes().await?;
5237- Ok(String::from_utf8_lossy(&raw_bytes).to_string())
5238- }
5239- }
5240-
5241- /// Collection of all the assets associated with the given theme that are
5242- /// served either from memory or on the file system.
5243- #[derive(Clone, Debug)]
5244- pub struct Assets(pub BTreeMap<String, Asset>);
5245-
5246- /// Theme is a tuple containing a unique name, a tera template, and an optional
5247- /// stylesheet. If no stylesheet is provided then the default base stylesheet
5248- /// that is compiled during compilation is served. If an additional stylesheet
5249- /// is loaded from disk then the default one and the custom one is served.
5250- #[derive(Clone)]
5251- pub struct Theme(pub Tera, pub Assets);
5252-
5253- /// Collection of themes which are a mixture of in-memory and user provided
5254- /// themes.
5255- #[derive(Clone)]
5256- pub struct Themes(pub BTreeMap<String, Theme>);
5257-
5258- /// load is responsible for constructing Tera templates which can come from
5259- /// built-in memory objects or from the file system in the case of user defined
5260- /// templates. User defined stylesheets and custom built-in themes both inherit
5261- /// configuration from the default theme.
5262- ///
5263- /// Order of theme loading:
5264- ///
5265- /// [default] # compiled in-memory
5266- /// |
5267- /// [built-in custom themes] # compiled in-memory
5268- /// |
5269- /// [user defined themes] # on disk
5270- pub fn load_themes(user_themes: Vec<(String, PathBuf)>) -> Result<Themes, Error> {
5271- let mut themes: BTreeMap<String, Theme> = BTreeMap::new();
5272- let mut default_tmpl = Tera::default();
5273- default_tmpl.register_filter("friendly_time", filters::FriendlyTime {});
5274- default_tmpl.register_filter("format_epoch", filters::FormatEpoch {});
5275- default_tmpl.register_filter("filemode", filters::FileMode {});
5276- default_tmpl.register_filter("human_bytes", filters::HumanBytes {});
5277-
5278- default_tmpl.add_raw_templates(
5279- BUILTIN_THEMES_DIR
5280- .find("default/templates/*.html")
5281- .unwrap()
5282- .filter_map(|tmpl_file| match tmpl_file.as_file() {
5283- Some(file) => {
5284- let file_name = file.path().file_name().unwrap().to_str().unwrap();
5285- Some((file_name.to_string(), file.contents_utf8().unwrap()))
5286- }
5287- None => None,
5288- }),
5289- )?;
5290-
5291- let mut default_assets = BTreeMap::from_iter(
5292- BUILTIN_THEMES_DIR
5293- .find("default/assets/*")
5294- .unwrap()
5295- .filter_map(|f| match f.as_file() {
5296- Some(file) => {
5297- let file_name = file.path().file_name().unwrap().to_str().unwrap();
5298- tracing::debug!("loaded asset: {}", file_name);
5299- Some((file_name.to_string(), Asset::Raw(file.contents().to_vec())))
5300- }
5301- None => None,
5302- }),
5303- );
5304-
5305- // base CSS theme all themes extend
5306- default_assets.insert(
5307- "main.min.css".to_string(),
5308- Asset::Raw(BASE_CSS_TEMPLATE.as_bytes().to_vec()),
5309- );
5310-
5311- let assets = Assets(default_assets.clone());
5312-
5313- default_tmpl.register_filter("emoji", filters::Emojis(assets.clone()));
5314- // load the default theme and it's stylesheet
5315- themes.insert(String::from("default"), Theme(default_tmpl.clone(), assets));
5316-
5317- // TODO: load other built-in themes
5318-
5319- // load user themes
5320- for (name, path) in user_themes {
5321- tracing::info!("loading user defined theme: {} @ {:?}", name, path);
5322-
5323- let templates_path = path.join("templates");
5324- let glob_pattern = format!("{}/*.html", templates_path.to_string_lossy());
5325- tracing::debug!("template glob pattern: {}", glob_pattern);
5326-
5327- let mut theme_templates = default_tmpl.clone();
5328-
5329- for template in glob_builder(glob_pattern)
5330- .max_depth(1)
5331- .file_type(FileType::FILE)
5332- .build()?
5333- {
5334- let template = template?;
5335- let template_name = template.file_name();
5336- let template_name = template_name.to_string_lossy().to_string();
5337- tracing::debug!("overriding template theme: {:?}", template_name);
5338- theme_templates.add_template_file(template.path(), Some(template_name.as_str()))?;
5339- }
5340-
5341- let assets_path = path.join("assets");
5342- let glob_pattern = format!("{}/*", assets_path.to_string_lossy());
5343- tracing::debug!("assets glob pattern: {}", glob_pattern);
5344-
5345- let theme_assets: Result<Vec<DirEntry>, globwalk::WalkError> = glob_builder(glob_pattern)
5346- .file_type(FileType::FILE)
5347- .max_depth(1)
5348- .build()?
5349- .collect();
5350-
5351- let mut theme_assets = BTreeMap::from_iter(theme_assets?.into_iter().map(|file| {
5352- let file_name = file.path().file_name().unwrap().to_str().unwrap();
5353- tracing::debug!("loaded theme asset {}", file_name);
5354- (file_name.to_string(), Asset::FilePath(file.into_path()))
5355- }));
5356-
5357- let mut base_assets = default_assets.clone();
5358-
5359- let theme_css_file = path.join("theme.css");
5360- if metadata(theme_css_file.as_path()).is_ok() {
5361- theme_assets.insert(
5362- "theme.css".to_string(),
5363- Asset::FilePath(theme_css_file.to_path_buf()),
5364- );
5365- tracing::debug!("extended theme with {:?}", theme_css_file);
5366- }
5367-
5368- base_assets.append(&mut theme_assets);
5369- let theme_assets = Assets(base_assets);
5370-
5371- theme_templates.register_filter("emoji", filters::Emojis(theme_assets.clone()));
5372- themes.insert(name.clone(), Theme(theme_templates, theme_assets));
5373- }
5374-
5375- Ok(Themes(themes))
5376- }
5377 diff --git a/ayllu/templates/404.html b/ayllu/templates/404.html
5378new file mode 100644
5379index 0000000..ef22b82
5380--- /dev/null
5381+++ b/ayllu/templates/404.html
5382 @@ -0,0 +1,9 @@
5383+ {% extends "base.html" %}
5384+ {% block content %}
5385+ <section class="stretch">
5386+ <h1>Not Found ({{ status_code }})</h1>
5387+ <p>Unable to find the resource you have requested, perhaps try looking somewhere else?</p>
5388+ <h4>Error Message:</h4>
5389+ <p>{{ error_message }}</p>
5390+ </section>
5391+ {% endblock %}
5392 diff --git a/ayllu/templates/5xx.html b/ayllu/templates/5xx.html
5393new file mode 100644
5394index 0000000..e0870d3
5395--- /dev/null
5396+++ b/ayllu/templates/5xx.html
5397 @@ -0,0 +1,9 @@
5398+ {% extends "base.html" %}
5399+ {% block content %}
5400+ <section class="strech">
5401+ <h1>Internal Service Error ({{ status_code }})</h1>
5402+ <p>Something has gone wrong processing your request, please be patient.</p>
5403+ <h4>Error Message:</h4>
5404+ <p>{{ error_message }}</p>
5405+ </section>
5406+ {% endblock %}
5407 diff --git a/ayllu/templates/about.html b/ayllu/templates/about.html
5408new file mode 100644
5409index 0000000..652e78a
5410--- /dev/null
5411+++ b/ayllu/templates/about.html
5412 @@ -0,0 +1,6 @@
5413+ {% extends "base.html" %}
5414+ {% block content %}
5415+ <section>
5416+ <article>{{ blurb | safe }}</article>
5417+ </section>
5418+ {% endblock %}
5419 diff --git a/ayllu/templates/authors.html b/ayllu/templates/authors.html
5420new file mode 100644
5421index 0000000..1fe63c5
5422--- /dev/null
5423+++ b/ayllu/templates/authors.html
5424 @@ -0,0 +1,20 @@
5425+ {% extends "base.html" %}
5426+ {% block content %}
5427+ <header>
5428+ <h1>Authors</h1>
5429+ </header>
5430+ {% for author in authors %}
5431+ <article>
5432+ <div class="wide">
5433+ <div>
5434+ <a href="/{{ collection }}/{{ name }}/log?username={{ author.username | urlencode }}&email={{ author.email | urlencode }}">{{ author.username }}</a>
5435+ </div>
5436+ <div>
5437+ <span class="positive">+{{ author.lines_added }}</span>
5438+ <span class="negative">-{{ author.lines_removed }}</span>
5439+ {{ author.percentage }}% ({{ author.count }})
5440+ </div>
5441+ </div>
5442+ </article>
5443+ {% endfor %}
5444+ {% endblock %}
5445 diff --git a/ayllu/templates/badge.svg b/ayllu/templates/badge.svg
5446new file mode 100644
5447index 0000000..895794b
5448--- /dev/null
5449+++ b/ayllu/templates/badge.svg
5450 @@ -0,0 +1,29 @@
5451+ <svg xmlns="http://www.w3.org/2000/svg"
5452+ xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20"
5453+ role="img" aria-label="{{key}}: {{value}}">
5454+ <title>{{key}}: {{value}}</title>
5455+ <linearGradient id="s" x2="0" y2="100%">
5456+ <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
5457+ <stop offset="1" stop-opacity=".1" />
5458+ </linearGradient>
5459+ <clipPath id="r">
5460+ <rect width="108" height="20" rx="3" fill="#fff" />
5461+ </clipPath>
5462+ <g clip-path="url(#r)">
5463+ <rect width="55" height="20" fill="#555" />
5464+ <rect x="55" width="53" height="20" fill="#007ec6" />
5465+ <rect width="108" height="20" fill="url(#s)" />
5466+ </g>
5467+ <g fill="#fff" text-anchor="middle"
5468+ font-family="monospace"
5469+ text-rendering="geometricPrecision" font-size="110">
5470+ <text aria-hidden="true" x="285" y="150" fill="#010101"
5471+ fill-opacity=".3" transform="scale(.1)" textLength="450">{{key}}</text>
5472+ <text x="285" y="140" transform="scale(.1)" fill="#fff"
5473+ textLength="450">{{key}}</text>
5474+ <text aria-hidden="true" x="805" y="150" fill="#010101"
5475+ fill-opacity=".3" transform="scale(.1)" textLength="430">{{value}}</text>
5476+ <text x="805" y="140" transform="scale(.1)" fill="#fff"
5477+ textLength="430">{{value}}</text>
5478+ </g>
5479+ </svg>
5480 diff --git a/ayllu/templates/base.html b/ayllu/templates/base.html
5481new file mode 100644
5482index 0000000..9768dbf
5483--- /dev/null
5484+++ b/ayllu/templates/base.html
5485 @@ -0,0 +1,30 @@
5486+ <!DOCTYPE html>
5487+ <html lang="en">
5488+ <meta name="description" content="{{ base.description }}">
5489+ <meta name="keywords" content="{{ base.keywords }}">
5490+ <head>
5491+ <meta charset="utf-8">
5492+ <meta name="viewport" content="width=device-width, initial-scale=1">
5493+ <title>
5494+ {% block title %}{{ base.title }}{% endblock %}
5495+ </title>
5496+ <link rel="stylesheet" href="/static/main.min.css" />
5497+ <link href="/static/logo.svg" rel="icon" type="image/svg+xml" />
5498+ {% block head %}{% endblock %}
5499+ </head>
5500+ <body>
5501+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1404468 -->
5502+ <script>0</script>
5503+ {% block navigation %}
5504+ {% include "nav.html" %}
5505+ {% endblock %}
5506+ <main>
5507+ <div class="container">
5508+ {% block content %}{% endblock %}
5509+ </div>
5510+ </main>
5511+ <footer>
5512+ Rendered {%- if let Some(render_time) = base.render_time %} in {{ render_time }} (ms){%- endif %} @ {{ base.current_time }}
5513+ </footer>
5514+ </body>
5515+ </html>
5516 diff --git a/ayllu/templates/blame.html b/ayllu/templates/blame.html
5517new file mode 100644
5518index 0000000..6e53e9a
5519--- /dev/null
5520+++ b/ayllu/templates/blame.html
5521 @@ -0,0 +1,33 @@
5522+ {% import "macros.html" as macros %}
5523+ {% extends "base.html" %}
5524+ {% block content %}
5525+ <header>
5526+ {% call macros::navigation(items=subnav_elements, title="Blame") %}
5527+ </header>
5528+ <section id="blame">
5529+ {% if is_renderable %}
5530+ <section id="blame-left">
5531+ <table>
5532+ <tbody>
5533+ {%- for line in blame_lines -%}
5534+ <tr>
5535+ <td>
5536+ {{ line.author_name }} <a href="/{{ collection }}/{{ name }}/commit/{{ line.commit_id }}">{{ line.timestamp }}</a>
5537+ </td>
5538+ {%- for space in line.spaces -%}
5539+ <tr>
5540+ <td>&nbsp;</td>
5541+ <td></td>
5542+ </tr>
5543+ {%- endfor -%}
5544+ </tr>
5545+ {% endfor %}
5546+ </tbody>
5547+ </table>
5548+ </section>
5549+ <section id="blame-right">{{ content.1 | safe }}</section>
5550+ {% else %}
5551+ <h2>This content is not blameable</h2>
5552+ {% endif %}
5553+ </section>
5554+ {% endblock %}
5555 diff --git a/ayllu/templates/blob.html b/ayllu/templates/blob.html
5556new file mode 100644
5557index 0000000..a967570
5558--- /dev/null
5559+++ b/ayllu/templates/blob.html
5560 @@ -0,0 +1,58 @@
5561+ {% import "macros.html" as macros %}
5562+ {% extends "base.html" %}
5563+ {% block content %}
5564+ <section id="blob" class="raised">
5565+ {% call macros::navigation(items=subnav_elements, title="Blob") %}
5566+ <section class="raised">
5567+ <section class="flex-group info-bar">
5568+ <section>
5569+ {%- if let Some((hint, color)) = hint -%}
5570+ <span class="hint" style="color: {{ color }}">{{ hint }}</span>
5571+ {%- endif -%}
5572+ <span>{{ file_name }}</span>
5573+ </section>
5574+ <section>
5575+ <span>{{ file_mode_ | file_mode }} {{ file_size | human_bytes }}</span>
5576+ </section>
5577+ </section>
5578+ <section id="code-viewer" class="scrollable lower-half">
5579+
5580+ {% if is_binary %}
5581+ <div class="blob-preview">
5582+ {% if is_image %}
5583+ <img height="1000px"
5584+ width="1000px"
5585+ alt="{{ file_name }}"
5586+ src="{{ raw_url }}">
5587+ </img>
5588+ {% elif is_video %}
5589+ <video controls>
5590+ <source src="{{ raw_url }}" type="{{ mime_type }}" />
5591+ <p>
5592+ <a href="{{ raw_url }}">raw url</a>
5593+ </p>
5594+ </video>
5595+ {% elif is_audio %}
5596+ <audio src="{{ raw_url }}" type="{{ mime_type }}"></audio>
5597+ {% else %}
5598+ <center>
5599+ <h3>Cannot render binary content</h3>
5600+ </center>
5601+ <a href="{{ raw_url }}">download</a>
5602+ {% endif %}
5603+ </div>
5604+ {% else %}
5605+ {% if is_markdown %}
5606+ {% if let Some(content) = content %}
5607+ <div class="readme">{{ content | safe }}</div>
5608+ {% endif %}
5609+ {% else %}
5610+ {% if let Some(content) = content %}
5611+ {{ content | safe }}
5612+ {% endif %}
5613+ {% endif %}
5614+ {% endif %}
5615+ </section>
5616+ </section>
5617+ </section>
5618+ {% endblock %}
5619 diff --git a/ayllu/templates/collection.html b/ayllu/templates/collection.html
5620new file mode 100644
5621index 0000000..c20c78f
5622--- /dev/null
5623+++ b/ayllu/templates/collection.html
5624 @@ -0,0 +1,37 @@
5625+ {% extends "base.html" %}
5626+ {% block content %}
5627+ <section class="raised">
5628+ <header>
5629+ <h1>
5630+ <a href="/{{ collection.name }}">{{ collection.name }}</a>
5631+ {%- if is_hidden %} <span class="negative">[hidden]</span> {%- endif -%}
5632+ </h1>
5633+ {% if let Some(description) = collection.description %}
5634+ {{ description }}
5635+ {% endif %}
5636+ </header>
5637+ <table class="data-table">
5638+ <thead>
5639+ <tr>
5640+ <th>Name</th>
5641+ <th>Description</th>
5642+ <th class="collapse">Last Updated</th>
5643+ </tr>
5644+ </thead>
5645+ <tbody>
5646+ {% for repo in collection.repositories %}
5647+ <tr>
5648+ <td>
5649+ <div class="name">
5650+ <a href="/{{ collection.name }}/{{ repo.name }}">{{ repo.name }}</a>
5651+ {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
5652+ </div>
5653+ </td>
5654+ <td>{{ repo.description }}</td>
5655+ <td>{{ repo.age }}</td>
5656+ </tr>
5657+ {% endfor %}
5658+ </tbody>
5659+ </table>
5660+ </section>
5661+ {% endblock %}
5662 diff --git a/ayllu/templates/commit.html b/ayllu/templates/commit.html
5663new file mode 100644
5664index 0000000..e74337f
5665--- /dev/null
5666+++ b/ayllu/templates/commit.html
5667 @@ -0,0 +1,70 @@
5668+ {% extends "base.html" %}
5669+ {% import "macros.html" as macros %}
5670+ {% block content %}
5671+ <section id="commit" class="raised">
5672+ <section class="info-bar">Commit</section>
5673+ <section class="lower-half">
5674+ <section id="commit-details" class="raised flex-group dense">
5675+ <section>
5676+ <p><b>Author:</b></p>
5677+ {% if distinct_author %}
5678+ <p><b>Committer:</b></p>
5679+ {% endif %}
5680+ <p><b>Hash:</b></p>
5681+ <p><b>Timestamp:</b></p>
5682+ <p>
5683+ <span class="positive">+{{ stats.insertions }}</span>
5684+ <span class="negative">-{{ stats.deletions }}</span>
5685+ <span>+/-{{ stats.files_changed }}</span>
5686+ <span>
5687+ <a href="/{{ collection }}/{{ name }}/tree/{{ commit_hash }}"
5688+ role="button">browse</a>
5689+ </span>
5690+ </p>
5691+ </section>
5692+ <section>
5693+ <p>
5694+ <a href="/{{ collection }}/{{ name }}/log?username={{ commit.author_name | urlencode }}&email={{ commit.author_email | urlencode }}">{{ commit.author_name }}</a> [<a href="mailto://{{ commit.author_email }}">{{ commit.author_email }}</a>]
5695+ </p>
5696+ {% if distinct_author %}
5697+ <p>
5698+ <a href="#">{{ commit.committer_name }}</a>
5699+ [<a href="mailto://{{ commit.committer_email }}">{{ commit.committer_email }}</a>]
5700+ {{ commit.committer_epoch | format_epoch }}
5701+ </p>
5702+ {% endif %}
5703+ <p class="{%- if let Some(_) = commit.is_verified -%} positive {%- else -%} negative {%- endif %} hash">{{ commit.id }}</p>
5704+ <p>{{ commit.author_epoch | format_epoch }} ({{ commit.epoch | friendly_time }})</p>
5705+ </section>
5706+ </section>
5707+
5708+ <section id="commit-message" class="raised">
5709+ <b>{{ commit.summary }}</b>
5710+ {% if extended_commit_message %}
5711+ <div class="message">
5712+ <pre>{{ commit.message }}</pre>
5713+ </div>
5714+ {% endif %}
5715+ {% if let Some(note) = note %}
5716+ <h5>Note</h5>
5717+ <section>
5718+ <span><b>Author:</b></span>
5719+ <span class="right">
5720+ <a href="/{{ collection }}/{{ name }}/log?username={{ note.author_name | urlencode }}&email={{ note.author_email | urlencode }}">{{ note.author_name }}</a>
5721+ [<a href="mailto://{{ note.author_email }}">{{ commit.author_email }}</a>]
5722+ </span>
5723+ </section>
5724+ <section>
5725+ <span><b>Timestamp:</b></span>
5726+ <span class="right">{{ note.author_epoch | format_epoch }} ({{ note.author_epoch | friendly_time }})</span>
5727+ </section>
5728+ <div class="message">
5729+ <pre>{{ note.message }}</pre>
5730+ </div>
5731+ {% endif %}
5732+ </section>
5733+
5734+ <section id="code-viewer" class="scrollable">{{ diff.1 | safe }}</section>
5735+ </section>
5736+ </section>
5737+ {% endblock %}
5738 diff --git a/ayllu/templates/config.html b/ayllu/templates/config.html
5739new file mode 100644
5740index 0000000..b39aa7e
5741--- /dev/null
5742+++ b/ayllu/templates/config.html
5743 @@ -0,0 +1,46 @@
5744+ {% extends "base.html" %}
5745+ {% block content %}
5746+ <section id="config-panel" class="raised">
5747+ <section class="info-bar">Configuration</section>
5748+ <section class="lower-half">
5749+ <form action="/config" method="post">
5750+ <label for="theme">
5751+ <h3>items-per-page</h3>
5752+ </label>
5753+ <select id="items_per_page" name="items_per_page" required>
5754+ <option value="50"
5755+ {%- if config.items_per_page == 50 -%}
5756+ selected
5757+ {%- endif -%}>50</option>
5758+ <option value="100"
5759+ {%- if config.items_per_page == 100 -%}
5760+ selected
5761+ {%- endif -%}>100</option>
5762+ <option value="150"
5763+ {%- if config.items_per_page == 150 -%}
5764+ selected
5765+ {%- endif -%}>150</option>
5766+ <option value="200"
5767+ {%- if config.items_per_page == 200 -%}
5768+ selected
5769+ {%- endif -%}>200</option>
5770+ </select>
5771+ <label for="theme">
5772+ <h3>theme</h3>
5773+ </label>
5774+ <select id="theme" name="theme" required>
5775+ {% for theme in themes %}
5776+ <option value="{{ theme }}"
5777+ {% if let Some(current_theme) = config.theme %}
5778+ {%- if theme == current_theme -%}
5779+ selected
5780+ {%- endif -%}
5781+ {%- endif -%}>{{ theme }}</option>
5782+ {% endfor %}
5783+ </select>
5784+ <!-- Button -->
5785+ <button type="submit">Submit</button>
5786+ </form>
5787+ </section>
5788+ </section>
5789+ {% endblock %}
5790 diff --git a/ayllu/templates/index.html b/ayllu/templates/index.html
5791new file mode 100644
5792index 0000000..2f67475
5793--- /dev/null
5794+++ b/ayllu/templates/index.html
5795 @@ -0,0 +1,50 @@
5796+ {% extends "base.html" %}
5797+ {% block content %}
5798+ <section id="index" class="raised">
5799+ <section class="flex-group">
5800+ <section>
5801+ <h2>Collections</h2>
5802+ </section>
5803+ <section id="rss-links">
5804+ {{ base.feed_icon | safe }}
5805+ <span>
5806+ [<a href="/rss/firehose.xml">*</a>,
5807+ <a href="/rss/1d.xml">1d</a>,
5808+ <a href="/rss/1w.xml">1w</a>,
5809+ <a href="/rss/1m.xml">1m</a>]
5810+ </span>
5811+ </section>
5812+ </section>
5813+ <section class="scrollable raised">
5814+ {% for collection in collections %}
5815+ <section class="info-bar">{{collection.name}}</section>
5816+ <section class="collection lower-half">
5817+ <table class="data-table">
5818+ <tbody id="{{ collection.name }}">
5819+ <tr class="group-label">
5820+ <th colspan="3">
5821+ <a class="collection" href="/{{ collection.name }}">{{ collection.name }}</a>
5822+ {% if let Some(description) = collection.description %}
5823+ {{ description }}
5824+ {% endif %}
5825+ </th>
5826+ </tr>
5827+ {% for repo in collection.repositories %}
5828+ <tr>
5829+ <td>
5830+ <div class="name">
5831+ <a href="/{{ collection.name }}/{{ repo.name }}">{{ repo.name }}</a>
5832+ {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
5833+ </div>
5834+ </td>
5835+ <td>{{ repo.description | truncate(50) }}</td>
5836+ <td class="collapse-xs">{{ repo.age }}</td>
5837+ </tr>
5838+ {% endfor %}
5839+ </tbody>
5840+ </table>
5841+ </section>
5842+ {% endfor %}
5843+ </section>
5844+ </section>
5845+ {% endblock %}
5846 diff --git a/ayllu/templates/log.html b/ayllu/templates/log.html
5847new file mode 100644
5848index 0000000..a0b89f0
5849--- /dev/null
5850+++ b/ayllu/templates/log.html
5851 @@ -0,0 +1,50 @@
5852+ {% import "macros.html" as macros %}
5853+ {% extends "base.html" %}
5854+ {% block content %}
5855+ <section id="log" class="scrollable raised">
5856+ {% if file_view %}
5857+ {% call macros::navigation(items=subnav_elements, title="Log") %}
5858+ {% endif %}
5859+ <section class="info-bar">Log</section>
5860+ <section class="lower-half">
5861+ <table class="data-table">
5862+ <thead>
5863+ <tr>
5864+ <th>ID</th>
5865+ <th>Flags</th>
5866+ <th>Age</th>
5867+ <th class="collapse">Author</th>
5868+ <th>Message</th>
5869+ </tr>
5870+ </thead>
5871+ <tbody>
5872+ {% for commit in commits %}
5873+ <tr>
5874+ <td>
5875+ <span class="{%- if let Some(_) = commit.is_verified -%}positive{%- else -%}negative {%- endif -%}">
5876+ <a href="/{{ collection }}/{{ name }}/commit/{{ commit.id }}">{{ commit.id | truncate(12) }}</a>
5877+ </span>
5878+ </td>
5879+ <td>
5880+ {% if let Some(_) = commit.has_note %}<div data-tooltip="Note">[N]</div>{% endif %}
5881+ {% if commit.is_extended %}<div data-tooltip="Extended Commit">[E]</div>{% endif %}
5882+ </td>
5883+ <td>{{ commit.epoch | friendly_time }}</td>
5884+ <td class="collapse">
5885+ <a href="/{{ collection }}/{{ name }}/log?username={{ commit.author_name | urlencode }}&email={{ commit.author_email | urlencode }}">{{ commit.author_name }}</a>
5886+ </td>
5887+ <td>{{ commit.summary | truncate(85) }}</td>
5888+ </tr>
5889+ {% endfor %}
5890+ </tbody>
5891+ </table>
5892+ {% if has_more -%}
5893+ <footer class="pagination">
5894+ {%- if let Some(last_commit) = last_commit -%}
5895+ <span class="right"><a href="/{{ collection }}/{{ name }}/log/{{ last_commit.id }}"><b>next</b></a></span>
5896+ {%- endif -%}
5897+ </footer>
5898+ {% endif %}
5899+ </section>
5900+ </section>
5901+ {% endblock %}
5902 diff --git a/ayllu/templates/macros.html b/ayllu/templates/macros.html
5903new file mode 100644
5904index 0000000..4d07b8c
5905--- /dev/null
5906+++ b/ayllu/templates/macros.html
5907 @@ -0,0 +1,16 @@
5908+ {% macro navigation(items, title="") %}
5909+ <nav class="subnav">
5910+ <ul>
5911+ <h4>{{ title }}</h4>
5912+ </ul>
5913+ <ul>
5914+ {% for item in items %}
5915+ <li {% if item.2 %}class="active"{% endif %}>
5916+ <a href="{{ item.1 }}">{{ item.0 }}</a>
5917+ </li>
5918+ {% endfor %}
5919+ </ul>
5920+ </nav>
5921+ {% endmacro navigation %}
5922+ {% macro commit_badge(commit, extended=false) %}
5923+ {% endmacro commit_badge %}
5924 diff --git a/ayllu/templates/nav.html b/ayllu/templates/nav.html
5925new file mode 100644
5926index 0000000..8ca9205
5927--- /dev/null
5928+++ b/ayllu/templates/nav.html
5929 @@ -0,0 +1,12 @@
5930+ <nav>
5931+ <a href="{%- if base.subpath_mode -%}/browse{%- else -%}/{%- endif -%}">
5932+ <div class="logo">{{ base.logo | safe }}</div>
5933+ </a>
5934+ <ul>
5935+ {% for item in base.nav_elements %}
5936+ <li {% if item.2 %}class="active"{% endif %}>
5937+ <a href="{{ item.1 }}">{{ item.0 }}</a>
5938+ </li>
5939+ {% endfor %}
5940+ </ul>
5941+ </nav>
5942 diff --git a/ayllu/templates/refs.html b/ayllu/templates/refs.html
5943new file mode 100644
5944index 0000000..e09930c
5945--- /dev/null
5946+++ b/ayllu/templates/refs.html
5947 @@ -0,0 +1,96 @@
5948+ {% import "macros.html" as macros %}
5949+ {% extends "base.html" %}
5950+ {% block content %}
5951+ <section id="refs-page">
5952+ <section id="refs" class="scrollable raised">
5953+ <section class="info-bar">Tags</section>
5954+ <section id="tags" class="lower-half">
5955+ <table class="data-table">
5956+ <thead>
5957+ <tr>
5958+ <th>Name</th>
5959+ <th class="collapse">Age</th>
5960+ <th class="collapse">Author</th>
5961+ <th>Archive</th>
5962+ </tr>
5963+ </thead>
5964+ <tbody>
5965+ {% for tag in tags %}
5966+ <tr>
5967+ <td>
5968+ <a href="/{{ collection }}/{{ name }}/refs/tag/{{ tag.name }}">{{ tag.name }}</a>
5969+ </td>
5970+ <td class="collapse">{{ tag.commit.epoch | friendly_time }}</td>
5971+ <td class="collapse">{{ tag.author_name }}</td>
5972+ <td>
5973+ <a href="/{{ collection }}/{{ name }}/refs/archive/{{ tag.name }}.tar.gz"
5974+ role="button">{{ tag.name }}.tar.gz</a>
5975+ </td>
5976+ </tr>
5977+ {% endfor %}
5978+ </tbody>
5979+ </table>
5980+ </section>
5981+ </section>
5982+ <section id="branches" class="scrollable raised">
5983+ <section class="info-bar">Branches</section>
5984+ <section id="branches" class="lower-half">
5985+ <table class="data-table">
5986+ <thead>
5987+ <tr>
5988+ <th>Name</th>
5989+ <th class="collapse">Age</th>
5990+ <th>Commit</th>
5991+ <th class="collapse-xs">Message</th>
5992+ </tr>
5993+ </thead>
5994+ <tbody>
5995+ {% for branch in branches %}
5996+ <tr>
5997+ <td>
5998+ <a href="/{{ collection }}/{{ name }}/tree/{{ branch.name | urlencode }}">{{ branch.name }}</a>
5999+ </td>
6000+ <td class="collapse">{{ branch.head.epoch | friendly_time }}</td>
6001+ <td>
6002+ <a href="/{{ collection }}/{{ name }}/commit/{{ branch.head.id }}">
6003+ {{ branch.head.id | truncate(8) }}</a>
6004+ </td>
6005+ <td class="collapse-xs">{{ branch.head.summary | truncate(85) }}</td>
6006+ </tr>
6007+ {% endfor %}
6008+ </tbody>
6009+ </table>
6010+ </section>
6011+ </section>
6012+ <section id="notes" class="scrollable raised">
6013+ <section class="info-bar">Notes</section>
6014+ <section id="notes" class="lower-half">
6015+ <table class="data-table">
6016+ <thead>
6017+ <tr>
6018+ <th>Commit</th>
6019+ <th>Author</th>
6020+ <th class="collapse">Age</th>
6021+ <th class="collapse-xs">Message</th>
6022+ </tr>
6023+ </thead>
6024+ <tbody>
6025+ {% for note in notes %}
6026+ <tr>
6027+ <td>
6028+ <a href="/{{ collection }}/{{ name }}/commit/{{ note.commit.id }}">
6029+ {{ note.commit.id | truncate(8) }}</a>
6030+ </td>
6031+ <td>{{ note.author_name }}</td>
6032+ <td class="collapse">{{ note.author_epoch | friendly_time }}</td>
6033+ <td class="collapse-xs">
6034+ <pre>{{ note.message | truncate(32) }}</pre>
6035+ </td>
6036+ </tr>
6037+ {% endfor %}
6038+ </tbody>
6039+ </table>
6040+ </section>
6041+ </section>
6042+ </section>
6043+ {% endblock %}
6044 diff --git a/ayllu/templates/repo.html b/ayllu/templates/repo.html
6045new file mode 100644
6046index 0000000..7798b07
6047--- /dev/null
6048+++ b/ayllu/templates/repo.html
6049 @@ -0,0 +1,110 @@
6050+ {% extends "base.html" %}
6051+ {% block content %}
6052+
6053+ <section>
6054+ <section class="info-bar">Project</section>
6055+ <section id="repo-action-panel" class="raised flex-group lower-half">
6056+ <section id="project-details" class="dense">
6057+ <p>
6058+ <span><b>Project: </b>{{ collection }}/{{ name }}</span>
6059+ </p>
6060+ {% if let Some(license) = license %}
6061+ <p>
6062+ <span> <b> License: </b> {{ license }} </span>
6063+ </p>
6064+ {% endif %}
6065+ {% if let Some(homepage) = homepage %}
6066+ <p>
6067+ <span> <b> Homepage: </b> <a href="{{homepage}}">{{ homepage }}</a></span>
6068+ </p>
6069+ {% endif %}
6070+ {% if let Some(description) = description %}
6071+ <p><span><b>Description: </b>{{description | truncate(45)}}</span></p>
6072+ {% endif %}
6073+ <section id="rss-links">
6074+ {{ base.feed_icon | safe }}
6075+ <span> [<a href="{{ rss_link_all }}">*</a>,
6076+ <a href="{{ rss_link_1d }}">1d</a>,
6077+ <a href="{{ rss_link_1w }}">1w</a>,
6078+ <a href="{{ rss_link_1m }}">1m</a>] </span>
6079+ </section>
6080+
6081+ </section>
6082+ <section id="clone-links">
6083+ <section class="clone-link">
6084+ <span><b>HTTP</b>:&nbsp</span><span class="url" aria-label="Repository URL">{{ http_clone_url }}</span>
6085+ </section>
6086+ {% if let Some(clone_url) = git_clone_url %}
6087+ <section class="clone-link">
6088+ <span><b>SSH</b>:&nbsp</span><span class="url" aria-label="Repository URL">{{ clone_url }}</span>
6089+ </section>
6090+ {% endif %}
6091+ </section>
6092+ </section>
6093+ </section>
6094+
6095+
6096+ <section id="tree" class="raised">
6097+ <section class="info-bar">Browse</section>
6098+ <section id="repo-details-panel" class="flex-group lower-half">
6099+ <section id="commit-summary">
6100+ {% if let Some(latest_commit) = latest_commit %}
6101+ <p class="dense">{{ latest_commit.author_name }}</p>
6102+ <p class="dense">{{ latest_commit.epoch | friendly_time }}</p>
6103+ <span {% if let Some(_) = latest_commit.is_verified -%} class="positive" {%- else -%} class="negative" {%- endif -%}><a href="/{{collection}}/{{name}}/commit/{{latest_commit.id}}">{{ latest_commit.id | truncate(12) }}</a></span>
6104+ <pre>{{ latest_commit.message }} </pre>
6105+ {% endif %}
6106+ </section>
6107+ <section id="ref-badge">
6108+ <span id="ref-badge">{{ refname }}</span>
6109+ </section>
6110+ </section>
6111+ <table class="data-table">
6112+ <thead>
6113+ <tr>
6114+ <th scope="col">File</th>
6115+ <th class="collapse-xs" scope="col">Commit</th>
6116+ <th class="collapse" scope="col">Size</th>
6117+ <th class="collapse" scope="col">Mode</th>
6118+ <th scope="col">Time</th>
6119+ </tr>
6120+ </thead>
6121+ <tbody>
6122+ {% for item in tree %}
6123+ <tr>
6124+ <td>
6125+ <a href="{{ item.0.name | make_object_url(item.0.kind, collection, name, refname, file_path) }}">
6126+ {{ item.0.name }}
6127+ {% if item.0.kind == "submodule" %}<span class="tiny">ref</span>{% endif %}
6128+ {% if item.0.kind == "pointer" %}<span class="tiny">ptr</span>{% endif %}
6129+ </a>
6130+ </td>
6131+ <td class="collapse-xs">
6132+ <a href="/{{ collection }}/{{ name }}/commit/{{ item.1.id }}">{{ item.1.summary | truncate(length=60) }}</a>
6133+ </td>
6134+ <td class="collapse">{{ item.0.size | human_bytes_i64 }}</td>
6135+ <td class="collapse">{{ item.0.mode | file_mode }}</td>
6136+ <td>
6137+ <a href="/{{ collection }}/{{ name }}/commit/{{ item.1.id }}">{{ item.1.epoch | friendly_time }}</a>
6138+ </td>
6139+ </tr>
6140+ {% endfor %}
6141+ </tbody>
6142+ </table>
6143+ </section>
6144+ {% if let Some(readme) = readme %}
6145+ <section class="raised">
6146+ {% if let Some(file_name) = rendered_file_name %}
6147+ <section class="flex-group info-bar">
6148+ <section>{{ file_name }}</section>
6149+ {% if let Some(rendered_file_size) = rendered_file_size %}
6150+ <section>{{rendered_file_size | human_bytes_i64}}</section>
6151+ {% endif %}
6152+ </section>
6153+ {% endif %}
6154+ <section id="readme" class="scrollable lower-half">
6155+ {{ readme | safe }}
6156+ </section>
6157+ {% endif %}
6158+ </section>
6159+ {% endblock %}
6160 diff --git a/ayllu/templates/rss_summary.html b/ayllu/templates/rss_summary.html
6161new file mode 100644
6162index 0000000..b23d638
6163--- /dev/null
6164+++ b/ayllu/templates/rss_summary.html
6165 @@ -0,0 +1,31 @@
6166+ <h1>Summary from {{ start_date }} to {{ end_date }}</h1>
6167+ <h2>
6168+ {{ n_tags }} tags and {{ n_commits }} commits added
6169+ {% if let Some(n_projects) = n_projects %}
6170+ across {{ n_projects }} projects{%- endif -%}
6171+ </h2>
6172+ {% for entry in entries %}
6173+ <h2>Updates For {{ entry.name }}</h2>
6174+ {% if let Some(n_tags) = entry.n_tags %}
6175+ </br>
6176+ <h3>
6177+ <u>{{ n_tags }} New Tags</u>
6178+ </h3>
6179+ {% endif %}
6180+ {% if let Some(commits) = entry.commits %}
6181+ </br>
6182+ <h3>
6183+ <u>New Commits</u>
6184+ </h3>
6185+ {% for commit in commits %}
6186+ <article>
6187+ <header>
6188+ <h4>
6189+ <a href="{{ origin }}/{{ entry.name }}/commit/{{ commit.id }}">{{ commit.summary }} - {{ commit.author_name }}</a>
6190+ </h4>
6191+ </header>
6192+ <pre>{{commit.message}}</pre>
6193+ </article>
6194+ {% endfor %}
6195+ {% endif %}
6196+ {% endfor %}
6197 diff --git a/ayllu/templates/tag.html b/ayllu/templates/tag.html
6198new file mode 100644
6199index 0000000..b97f91d
6200--- /dev/null
6201+++ b/ayllu/templates/tag.html
6202 @@ -0,0 +1,33 @@
6203+ {% import "macros.html" as macros %}
6204+ {% extends "base.html" %}
6205+ {% block content %}
6206+ <section class="raised">
6207+ <h1>{{ tag.name }}</h1>
6208+ <section>
6209+ <span><b>Author:</b></span>
6210+ <span class="right">
6211+ <a href="/{{ collection }}/{{ name }}/log?username={{ tag.commit.author_name | urlencode }}&email={{ tag.commit.author_email | urlencode }}">{{ tag.commit.author_name }}</a>
6212+ [<a href="mailto://{{ tag.commit.author_email }}">{{ tag.commit.author_email }}</a>]
6213+ </span>
6214+ </section>
6215+ <section>
6216+ <span><b>Hash:</b></span>
6217+ <span class="right {% if let Some(is_verified) = tag.commit.is_verified -%} positive {%- else -%} negative {%- endif -%}">{{ tag.commit.id }}</span>
6218+ </section>
6219+ <section>
6220+ <span><b>Timestamp:</b></span>
6221+ <span class="right">{{ tag.commit.author_epoch | format_epoch }} ({{ tag.commit.epoch | friendly_time }})</span>
6222+ </section>
6223+ <section>
6224+ <span><b>Archive:</b></span>
6225+ <span class="right">
6226+ <a href="/{{ collection }}/{{ name }}/refs/archive/{{ tag.name }}.tar.gz"
6227+ role="button">{{ tag.name }}.tar.gz</a>
6228+ </span>
6229+ </section>
6230+ <h4>Summary</h4>
6231+ <pre>
6232+ {{tag.summary}}
6233+ </pre>
6234+ </section>
6235+ {% endblock %}
6236 diff --git a/ayllu/themes/base.css b/ayllu/themes/base.css
6237new file mode 100644
6238index 0000000..3c4d2ce
6239--- /dev/null
6240+++ b/ayllu/themes/base.css
6241 @@ -0,0 +1,408 @@
6242+ :root {
6243+ font-size: 80%;
6244+ }
6245+
6246+ main {
6247+ margin: 0;
6248+ min-height: 90vh;
6249+ }
6250+
6251+ .content {
6252+ width: 100%;
6253+ }
6254+
6255+ .container {
6256+ margin: 0 auto;
6257+ padding-top: 25px;
6258+ max-width: 1280px;
6259+ }
6260+
6261+ .data-table {
6262+ white-space: nowrap;
6263+ }
6264+
6265+ section#log > section >.data-table > tbody > tr > td:first-child {
6266+ text-align:center;
6267+ }
6268+
6269+ a:visited {
6270+ text-decoration: none;
6271+ }
6272+
6273+ table {
6274+ min-width: 100%;
6275+ }
6276+
6277+ nav {
6278+ display: flex;
6279+ justify-content: space-between;
6280+ align-items: center;
6281+ padding: 1em;
6282+ border-radius: 5px;
6283+ }
6284+
6285+ nav .logo {
6286+ width: 100px;
6287+ font-size: 20px;
6288+ font-weight: bold;
6289+ }
6290+
6291+ nav ul {
6292+ list-style-type: none;
6293+ margin: 0;
6294+ padding: 0;
6295+ display: flex;
6296+ }
6297+
6298+ nav ul li {
6299+ margin-left: 20px;
6300+ }
6301+
6302+ nav ul li a {
6303+ text-decoration: none;
6304+ }
6305+
6306+ span.hint {
6307+ padding: 4px;
6308+ font-weight: bold;
6309+ text-decoration: underline;
6310+ }
6311+
6312+
6313+ header {
6314+ margin-bottom: 1em;
6315+ }
6316+
6317+ section.info-bar {
6318+ border-radius: revert;
6319+ border-top: solid 2px;
6320+ border-left: solid 2px;
6321+ border-right: solid 2px;
6322+ border-bottom: none;
6323+ padding: 5px;
6324+ font-weight: bold;
6325+ font-size: large;
6326+ font-family: monospace;
6327+ text-decoration: underline;
6328+ }
6329+
6330+ section#code-viewer {
6331+ padding: 1em;
6332+ }
6333+
6334+ span#ref-badge {
6335+ font-weight: bold;
6336+ border-width: 2px;
6337+ border-style: solid;
6338+ border-radius: 5px;
6339+ padding: 2px;
6340+ }
6341+
6342+ section#commit-details {
6343+ padding: 1em;
6344+ margin-bottom: 0;
6345+ }
6346+
6347+ section#commit-message {
6348+ padding: 1em;
6349+ font-size: var(--font-size-0);
6350+ font-weight: var(--font-weight-0);
6351+ margin-bottom: 0;
6352+ }
6353+
6354+ span#ref-badge::before {
6355+ content: "Ref:: ";
6356+ }
6357+
6358+ section#rss-links {
6359+ display: flex;
6360+ align-items: center;
6361+ width: 145px;
6362+ }
6363+
6364+ section#rss-links > svg {
6365+ margin-right: auto;
6366+ }
6367+
6368+ section#rss-links > span {
6369+ text-align: center;
6370+ float: right;
6371+ }
6372+
6373+ section#readme {
6374+
6375+ padding: 1em;
6376+
6377+ .p {
6378+ margin: .5em;
6379+ }
6380+
6381+ :where(img) {
6382+ display: revert;
6383+ }
6384+
6385+ h1,
6386+ h2,
6387+ h3,
6388+ h4,
6389+ h5,
6390+ h6 {
6391+ margin-bottom: .5em;
6392+ margin-top: .5em;
6393+ }
6394+
6395+ table.code {
6396+ font-family: var(--font-mono);
6397+ width: 100%;
6398+ margin-top: 1em;
6399+ text-align: left;
6400+ }
6401+
6402+ header {
6403+ display: flex;
6404+ align-items: center;
6405+ }
6406+
6407+ table {
6408+ min-width: initial;
6409+ }
6410+
6411+ .icon-link {
6412+ display: flex;
6413+ align-items: center;
6414+ }
6415+
6416+ .icon {
6417+ width: 24px;
6418+ height: 24px;
6419+ fill: currentColor;
6420+ }
6421+ }
6422+
6423+ .dense {
6424+ line-height: 5px;
6425+ }
6426+
6427+ section {
6428+ border-radius: 5px;
6429+ }
6430+
6431+ section#index {
6432+ padding: 1em;
6433+ }
6434+
6435+ section#refs-page {
6436+ padding: 1em;
6437+ }
6438+
6439+ section#repo-action-panel {
6440+ padding: 1em;
6441+ }
6442+
6443+ section#repo-details-panel {
6444+ padding: 1em;
6445+ }
6446+
6447+ section#config-panel {
6448+ max-width: 400px;
6449+ }
6450+
6451+ section#config-panel >section {
6452+ padding: 1em;
6453+ }
6454+
6455+ section#config-panel > form > button {
6456+ margin-top: 1em;
6457+ display: block;
6458+ }
6459+
6460+ section.raised {
6461+ margin-bottom: 1em;
6462+ }
6463+
6464+ section.flex-group {
6465+ display: flex;
6466+ justify-content: space-between;
6467+ }
6468+
6469+ section.clone-link {
6470+ display: flex;
6471+ align-items: center;
6472+ justify-content: space-between;
6473+ width: auto;
6474+ padding: 8px 12px;
6475+ font-size: 14px;
6476+ line-height: 1.5;
6477+ border: none;
6478+ border-radius: 3px;
6479+ cursor: pointer;
6480+ text-decoration: none;
6481+ }
6482+
6483+ section.clone-link > .url {
6484+ padding: 2px;
6485+ }
6486+
6487+ section.clone-link {
6488+ border: solid 2px;
6489+ }
6490+
6491+ section#clone-links > .clone-link {
6492+ margin: 5px;
6493+ }
6494+
6495+ section.clone-link > .url {
6496+ -webkit-user-select: text;
6497+ -moz-user-select: text;
6498+ -ms-user-select: text;
6499+ user-select: text;
6500+ }
6501+
6502+ footer {
6503+ padding-top: 2em;
6504+ text-align: center;
6505+ font-size: smaller;
6506+ }
6507+
6508+ footer.rss {
6509+ text-align: unset;
6510+ }
6511+
6512+ .scrollable {
6513+ overflow: scroll;
6514+ }
6515+
6516+ .blob-preview {
6517+ text-align: center;
6518+ border-style: solid;
6519+ margin-top: 2em;
6520+ padding: 1em;
6521+ }
6522+
6523+ .line-number {
6524+ user-select: none;
6525+ width: 0.5em;
6526+ }
6527+
6528+ .line {
6529+ white-space: pre;
6530+ padding-left: 5px;
6531+ }
6532+
6533+ .positive {
6534+ text-decoration: underline dotted green;
6535+ }
6536+
6537+ .negative {
6538+ text-decoration: underline dotted green;
6539+ }
6540+
6541+ section.lower-half {
6542+ border: solid 2px;
6543+ border-radius: unset;
6544+ }
6545+
6546+ section#blame-left > table {
6547+ font-family: var(--font-mono);
6548+ }
6549+
6550+ section#tree {
6551+ table {
6552+ border-collapse: collapse;
6553+ border: solid 2px;
6554+ background: none;
6555+ white-space: nowrap;
6556+ font-family: var(--tree-font);
6557+ }
6558+
6559+ table :is(td, th) {
6560+ padding: 0;
6561+ /* border-block: 10px solid pink; */
6562+ border-radius: 0;
6563+ white-space: nowrap;
6564+ text-align: start;
6565+ }
6566+
6567+ table :is(td, th):nth-child(1) {
6568+ padding-left: 5px;
6569+ }
6570+
6571+ table :is(td, th):nth-child(5) {
6572+ padding-right: 5px;
6573+ }
6574+
6575+ table :is(td, th):nth-child(2) {
6576+ overflow: hidden;
6577+ max-inline-size: 70ch;
6578+ white-space: nowrap;
6579+ text-overflow: ellipsis;
6580+ }
6581+
6582+ table :is(td, th):last-of-type {
6583+ text-align: end;
6584+ }
6585+ }
6586+
6587+ code.highlighted {
6588+ width: 100%;
6589+ overflow: scroll;
6590+ }
6591+
6592+ .code > tbody > tr > td {
6593+ text-align: revert;
6594+ padding: 0 0 0 0;
6595+ padding-right: 0.5em;
6596+ }
6597+
6598+ svg#emoji {
6599+ max-width: 40px;
6600+ }
6601+
6602+ .card {
6603+ margin: 0px;
6604+ padding: 0px;
6605+ }
6606+
6607+ li.active {
6608+ text-decoration: underline;
6609+ }
6610+
6611+
6612+
6613+ img.rss-icon-feed {
6614+ height: 4em;
6615+ }
6616+
6617+ :where(td, th) {
6618+ padding: 5px;
6619+ }
6620+
6621+ :where(:is(td, th):not([align])) {
6622+ text-align: left;
6623+ }
6624+
6625+ :where(table) {
6626+ border-radius: unset;
6627+ --nice-inner-radius: none;
6628+ }
6629+
6630+ @media (max-width: 800px) {
6631+ section.flex-group {
6632+ flex-direction: column;
6633+ }
6634+
6635+ section.flex-group > section {
6636+ width: 100%;
6637+ height: auto;
6638+ }
6639+
6640+ .collapse {
6641+ display: none;
6642+ }
6643+ }
6644+
6645+ @media (max-width: 600px) {
6646+ .collapse-xs {
6647+ display: none;
6648+ }
6649+ }
6650 diff --git a/ayllu/themes/catppuccin.css b/ayllu/themes/catppuccin.css
6651new file mode 100644
6652index 0000000..7e3ba30
6653--- /dev/null
6654+++ b/ayllu/themes/catppuccin.css
6655 @@ -0,0 +1,288 @@
6656+ /* https://github.com/catppuccin/palette/tree/main */
6657+
6658+ :root {
6659+ --ctp-latte-rosewater: #dc8a78;
6660+ --ctp-latte-flamingo: #dd7878;
6661+ --ctp-latte-pink: #ea76cb;
6662+ --ctp-latte-mauve: #8839ef;
6663+ --ctp-latte-red: #d20f39;
6664+ --ctp-latte-maroon: #e64553;
6665+ --ctp-latte-peach: #fe640b;
6666+ --ctp-latte-yellow: #df8e1d;
6667+ --ctp-latte-green: #40a02b;
6668+ --ctp-latte-teal: #179299;
6669+ --ctp-latte-sky: #04a5e5;
6670+ --ctp-latte-sapphire: #209fb5;
6671+ --ctp-latte-blue: #1e66f5;
6672+ --ctp-latte-lavender: #7287fd;
6673+ --ctp-latte-text: #4c4f69;
6674+ --ctp-latte-subtext1: #5c5f77;
6675+ --ctp-latte-subtext0: #6c6f85;
6676+ --ctp-latte-overlay2: #7c7f93;
6677+ --ctp-latte-overlay1: #8c8fa1;
6678+ --ctp-latte-overlay0: #9ca0b0;
6679+ --ctp-latte-surface2: #acb0be;
6680+ --ctp-latte-surface1: #bcc0cc;
6681+ --ctp-latte-surface0: #ccd0da;
6682+ --ctp-latte-base: #eff1f5;
6683+ --ctp-latte-mantle: #e6e9ef;
6684+ --ctp-latte-crust: #dce0e8;
6685+ }
6686+
6687+ :root {
6688+ --ctp-mocha-rosewater: #f5e0dc;
6689+ --ctp-mocha-flamingo: #f2cdcd;
6690+ --ctp-mocha-pink: #f5c2e7;
6691+ --ctp-mocha-mauve: #cba6f7;
6692+ --ctp-mocha-red: #f38ba8;
6693+ --ctp-mocha-maroon: #eba0ac;
6694+ --ctp-mocha-peach: #fab387;
6695+ --ctp-mocha-yellow: #f9e2af;
6696+ --ctp-mocha-green: #a6e3a1;
6697+ --ctp-mocha-teal: #94e2d5;
6698+ --ctp-mocha-sky: #89dceb;
6699+ --ctp-mocha-sapphire: #74c7ec;
6700+ --ctp-mocha-blue: #89b4fa;
6701+ --ctp-mocha-lavender: #b4befe;
6702+ --ctp-mocha-text: #cdd6f4;
6703+ --ctp-mocha-subtext1: #bac2de;
6704+ --ctp-mocha-subtext0: #a6adc8;
6705+ --ctp-mocha-overlay2: #9399b2;
6706+ --ctp-mocha-overlay1: #7f849c;
6707+ --ctp-mocha-overlay0: #6c7086;
6708+ --ctp-mocha-surface2: #585b70;
6709+ --ctp-mocha-surface1: #45475a;
6710+ --ctp-mocha-surface0: #313244;
6711+ --ctp-mocha-base: #1e1e2e;
6712+ --ctp-mocha-mantle: #181825;
6713+ --ctp-mocha-crust: #11111b;
6714+ }
6715+
6716+ a {
6717+ color: var(--ctp-latte-blue);
6718+ }
6719+
6720+ body {
6721+ color: var(--ctp-latte-text);
6722+ background-color: var(--ctp-latte-base);
6723+ }
6724+
6725+ nav {
6726+ a {
6727+ color: var(--ctp-latte-sky);
6728+ font-weight: bold;
6729+ font-size: large;
6730+ }
6731+ color: var(--ctp-latte-text);
6732+ background-color: var(--ctp-latte-mantle);
6733+ }
6734+
6735+ nav .subnav {
6736+ background-color: var(--ctp-latte-mantle);
6737+ }
6738+
6739+ section.raised {
6740+ background-color: var(--ctp-latte-crust);
6741+ }
6742+
6743+ .data-table thead {
6744+ color: var(--ctp-latte-surface0);
6745+ background-color: var(--ctp-latte-lavender);
6746+ }
6747+
6748+ .data-table tbody tr:nth-child(odd) {
6749+ background-color: var(--ctp-latte-surface0);
6750+ }
6751+
6752+ .data-table tbody tr:nth-child(even) {
6753+ background-color: var(--ctp-latte-surface1);
6754+ }
6755+
6756+ section#clone-links > .clone-link {
6757+ color: var(--ctp-latte-base);
6758+ border-color: var(--ctp-latte-overlay2);
6759+ background-color: var(--ctp-latte-lavender);
6760+ }
6761+
6762+ section#readme-header {
6763+ color: var(--ctp-latte-base);
6764+ background-color: var(--ctp-latte-lavender);
6765+ }
6766+
6767+ span#ref-badge {
6768+ background-color: var(--ctp-latte-lavender);
6769+ color: var(--ctp-latte-base);
6770+ border-color: var(--ctp-latte-overlay2);
6771+ }
6772+
6773+ span#ref-badge::before {
6774+ background-color: var(--ctp-latte-green);
6775+ }
6776+
6777+ .positive {
6778+ color: var(--ctp-latte-green);
6779+ border-bottom: 2px solid var(--ctp-latte-green);
6780+ }
6781+
6782+ .negative {
6783+ color: var(--ctp-latte-maroon);
6784+ border-bottom: 2px solid var(--ctp-latte-maroon);
6785+ }
6786+
6787+ section.info-bar {
6788+ background-color: var(--ctp-latte-surface0);
6789+ color: var(--ctp-latte-text);
6790+ border-color: var(--ctp-latte-lavender);
6791+ }
6792+
6793+ section.lower-half {
6794+ border-color: var(--ctp-latte-lavender);
6795+ }
6796+
6797+ /* Syntax Highlighting */
6798+
6799+ section#code-viewer {
6800+ color: var(--ctp-latte-text);
6801+ background-color: var(--ctp-latte-base);
6802+ }
6803+
6804+ /*
6805+ span.ts_operator {}
6806+ span.ts_property {}
6807+ span.ts_punctuation {}
6808+ span.ts_punctuation_bracket {}
6809+ span.ts_punctuation_delimiter {}
6810+ span.ts_variable_builtin {}
6811+ span.ts_variable_parameter {}
6812+ */
6813+
6814+ span.ts_attribute {
6815+ color: var(--ctp-latte-flamingo);
6816+ }
6817+
6818+ span.ts_comment {
6819+ color: var(--ctp-latte-subtext0);
6820+ }
6821+
6822+ span.ts_constant {
6823+ color: var(--ctp-latte-rosewater);
6824+ }
6825+
6826+ span.ts_function_builtin {
6827+ color: var(--ctp-latte-flamingo);
6828+ }
6829+
6830+ span.ts_function {
6831+ color: var(--ctp-latte-lavender);
6832+ }
6833+
6834+ span.ts_keyword {
6835+ color: var(--ctp-latte-rosewater);
6836+ }
6837+
6838+ span.ts_string {
6839+ color: var(--ctp-latte-subtext1);
6840+ }
6841+
6842+ span.ts_string_special {
6843+ color: var(--ctp-latte-teal);
6844+ }
6845+
6846+ span.ts_tag {
6847+ color: var(--ctp-latte-peach);
6848+ }
6849+
6850+ span.ts_type {
6851+ color: var(--ctp-latte-pink);
6852+ }
6853+
6854+ span.ts_type_builtin {
6855+ color: var(--ctp-latte-mauve);
6856+ }
6857+
6858+ span.ts_variable {
6859+ color: var(--ctp-latte-rosewater);
6860+ }
6861+
6862+ span.ts_removal {
6863+ color: var(--ctp-latte-red);
6864+ }
6865+
6866+ span.ts_addition {
6867+ color: var(--ctp-latte-green);
6868+ }
6869+
6870+ /* Dark Mode */
6871+
6872+ @media (prefers-color-scheme: dark) {
6873+ a {
6874+ color: var(--ctp-mocha-blue);
6875+ }
6876+
6877+ nav {
6878+ a {
6879+ color: var(--ctp-mocha-blue);
6880+ font-weight: bold;
6881+ font-size: large;
6882+ }
6883+ color: var(--ctp-mocha-green);
6884+ background-color: var(--ctp-mocha-crust);
6885+ }
6886+
6887+ nav .subnav {
6888+ background-color: var(--ctp-latte-mantle);
6889+ }
6890+
6891+ body {
6892+ color: var(--ctp-mocha-text);
6893+ background-color: var(--ctp-mocha-base);
6894+ }
6895+
6896+ section#clone-links > .clone-link {
6897+ color: var(--ctp-mocha-base);
6898+ background-color: var(--ctp-mocha-lavender);
6899+ }
6900+
6901+ section.raised {
6902+ background-color: var(--ctp-mocha-surface1);
6903+ }
6904+
6905+ section#readme-header {
6906+ background-color: var(--ctp-mocha-blue);
6907+ }
6908+
6909+ .data-table {
6910+ color: var(--ctp-mocha-mauve);
6911+ a {
6912+ color: var(--ctp-mocha-blue);
6913+ }
6914+ }
6915+
6916+ .data-table thead {
6917+ color: var(--ctp-mocha-surface0);
6918+ background-color: var(--ctp-mocha-lavender);
6919+ }
6920+
6921+ .data-table tbody tr:nth-child(odd) {
6922+ background-color: var(--ctp-mocha-surface1);
6923+ }
6924+
6925+ .data-table tbody tr:nth-child(even) {
6926+ background-color: var(--ctp-mocha-surface2);
6927+ }
6928+
6929+ section#code-viewer {
6930+ color: var(--ctp-mocha-text);
6931+ background-color: var(--ctp-mocha-crust);
6932+ }
6933+
6934+ section.info-bar {
6935+ background-color: var(--ctp-mocha-mauve);
6936+ color: var(--ctp-mocha-crust);
6937+ border-color: var(--ctp-mocha-mauve);
6938+ }
6939+
6940+ section.lower-half {
6941+ border-color: var(--ctp-mocha-mauve);
6942+ }
6943+ }
6944 diff --git a/ayllu/themes/default.css b/ayllu/themes/default.css
6945new file mode 100644
6946index 0000000..07d26ef
6947--- /dev/null
6948+++ b/ayllu/themes/default.css
6949 @@ -0,0 +1,107 @@
6950+ :root {
6951+ --primary-color: #000;
6952+ --secondary-color: #fff;
6953+ --tertiary-color: pink;
6954+ }
6955+
6956+ @media (prefers-color-scheme: dark) {
6957+ :root {
6958+ --primary-color: #fff;
6959+ --secondary-color: #000;
6960+ }
6961+ }
6962+
6963+ body {
6964+ color: var(--primary-color);
6965+ background-color: var(--secondary-color);
6966+ }
6967+
6968+ /* Syntax Highlighting */
6969+
6970+ span.ts_attribute {
6971+ color: var(--primary-color);
6972+ }
6973+
6974+ span.ts_comment {
6975+ color: var(--primary-color);
6976+ }
6977+
6978+ span.ts_constant {
6979+ color: var(--primary-color);
6980+ }
6981+
6982+ span.ts_function_builtin {
6983+ color: var(--primary-color);
6984+ }
6985+
6986+ span.ts_function {
6987+ color: var(--primary-color);
6988+ }
6989+
6990+ span.ts_keyword {
6991+ color: var(--primary-color);
6992+ }
6993+
6994+ span.ts_operator {
6995+ color: var(--primary-color);
6996+ }
6997+
6998+ span.ts_property {
6999+ color: var(--primary-color);
7000+ }
7001+
7002+ span.ts_punctuation {
7003+ color: var(--primary-color);
7004+ }
7005+
7006+ span.ts_punctuation_bracket {
7007+ color: var(--primary-color);
7008+ }
7009+
7010+ span.ts_punctuation_delimiter {
7011+ color: var(--primary-color);
7012+ }
7013+
7014+ span.ts_punctuation_delimiter {
7015+ color: var(--primary-color);
7016+ }
7017+
7018+ span.ts_string {
7019+ color: var(--primary-color);
7020+ }
7021+
7022+ span.ts_string_special {
7023+ color: var(--primary-color);
7024+ }
7025+
7026+ span.ts_tag {
7027+ color: var(--primary-color);
7028+ }
7029+
7030+ span.ts_type {
7031+ color: var(--primary-color);
7032+ }
7033+
7034+ span.ts_type_builtin {
7035+ color: var(--primary-color);
7036+ }
7037+
7038+ span.ts_variable {
7039+ color: var(--primary-color);
7040+ }
7041+
7042+ span.ts_variable_builtin {
7043+ color: var(--primary-color);
7044+ }
7045+
7046+ span.ts_variable_parameter {
7047+ color: var(--primary-color);
7048+ }
7049+
7050+ span.ts_removal {
7051+ color: var(--primary-color);
7052+ }
7053+
7054+ span.ts_addition {
7055+ color: var(--primary-color);
7056+ }
7057 diff --git a/ayllu/themes/default/assets/anchor.svg b/ayllu/themes/default/assets/anchor.svg
7058deleted file mode 100644
7059index 62454e3..0000000
7060--- a/ayllu/themes/default/assets/anchor.svg
7061+++ /dev/null
7062 @@ -1 +0,0 @@
7063- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon" width="24" height="24" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .75.75 0 0 1 .018-1.042.75.75 0 0 1 1.042-.018 2 2 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.75.75 0 0 1-1.042-.018.75.75 0 0 1-.018-1.042m-4.69 9.64a2 2 0 0 0 2.83 0l1.25-1.25a.75.75 0 0 1 1.042.018.75.75 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .75.75 0 0 1-.018 1.042.75.75 0 0 1-1.042.018 2 2 0 0 0-2.83 0l-2.5 2.5a2 2 0 0 0 0 2.83"></path></svg>
7064 diff --git a/ayllu/themes/default/assets/books.svg b/ayllu/themes/default/assets/books.svg
7065deleted file mode 100644
7066index ba393a5..0000000
7067--- a/ayllu/themes/default/assets/books.svg
7068+++ /dev/null
7069 @@ -1,16 +0,0 @@
7070- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7071- <g id="color">
7072- <path fill="#fff" d="M61.2167,21.6411,37.911,15.0386a1.0068,1.0068,0,0,0-.5537.0019l-22.5947,6.627a.9484.9484,0,0,0-.1365.0719,5.3829,5.3829,0,0,0-1.8343,9.5426,5.3642,5.3642,0,0,0,.0663,8.6968,5.3776,5.3776,0,0,0,1.4856,9.5022l23.0146,6.7128a1.0034,1.0034,0,0,0,.5547.002l23.3057-6.6514a1.0008,1.0008,0,0,0,.7255-.9619V22.603A1,1,0,0,0,61.2167,21.6411Z"/>
7073- <polygon fill="#92d3f5" points="37.638 15.976 60.944 22.579 37.638 29.231 15.044 22.603 37.638 15.976"/>
7074- <path fill="#61b2e4" d="M15.0823,24.7275,37.0756,31.174a2,2,0,0,0,1.1113.0039l23.2947-6.7653a.613.613,0,0,0,.442-.5794l.0189-1.2508L37.6381,29.2547,15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2423a.6255.6255,0,0,0-.7968-.6215L37.6442,35.7552l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7075- <path fill="#d22f27" d="M15.0823,33.4092l21.9933,6.4465a1.9988,1.9988,0,0,0,1.1113.004l23.2947-6.7653a.6131.6131,0,0,0,.442-.5794l.0189-1.2508L37.6381,37.9365,15.08,31.3243a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,44.437l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7076- <path fill="#5c9e31" d="M15.0823,42.1549l21.9933,6.4465a2,2,0,0,0,1.1113.0039L61.4816,41.84a.613.613,0,0,0,.442-.5794l.0189-1.2507L37.6381,46.6821,15.08,40.07a4.4,4.4,0,0,0-.4554,8.4813l23.014,6.713,24.2115-6.91.04-1.2424a.6254.6254,0,0,0-.7968-.6214L37.6442,53.1827l-22.4226-6.54a2.4,2.4,0,0,1-.1393-4.4873"/>
7077- <polygon fill="#61b2e4" points="44.32 17.794 38.594 16.172 16 22.799 21.81 24.504 44.32 17.794"/>
7078- </g>
7079- <g id="line">
7080- <polygon fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="37.638 16 60.944 22.603 37.638 29.255 15.044 22.627 37.638 16"/>
7081- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,22.6425a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7082- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,31.2942a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7083- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.08,40.04a4.4,4.4,0,0,0-.4554,8.4813l23.0141,6.713,23.3057-6.6516"/>
7084- </g>
7085- </svg>
7086 diff --git a/ayllu/themes/default/assets/building.svg b/ayllu/themes/default/assets/building.svg
7087deleted file mode 100644
7088index f57946b..0000000
7089--- a/ayllu/themes/default/assets/building.svg
7090+++ /dev/null
7091 @@ -1,26 +0,0 @@
7092- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7093- <g id="color">
7094- <rect x="18.54" y="26" width="34.92" height="25.95" fill="#9b9b9a"/>
7095- <rect x="12" y="56" width="48" height="4" fill="#fff" stroke-miterlimit="10"/>
7096- <rect x="14.13" y="22" width="43.74" height="4" fill="#fff" stroke-miterlimit="10"/>
7097- <rect x="15.72" y="52" width="40.55" height="4" fill="#fff" stroke-miterlimit="10"/>
7098- <rect x="18.5" y="26" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7099- <rect x="27.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7100- <rect x="49.52" y="25.95" width="3.941" height="26" fill="#fff" stroke-miterlimit="10"/>
7101- <polygon fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="36 12 19 21.75 53 21.75"/>
7102- <rect x="40.35" y="26.15" width="4" height="26" fill="#fff" stroke-miterlimit="10"/>
7103- </g>
7104- <g id="hair"/>
7105- <g id="skin"/>
7106- <g id="skin-shadow"/>
7107- <g id="line">
7108- <rect x="11.99" y="55.89" width="48.01" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7109- <rect x="14.18" y="21.76" width="43.65" height="3.999" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2.001"/>
7110- <rect x="15.27" y="51.76" width="41.47" height="3.993" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7111- <rect x="18.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7112- <rect x="27.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7113- <rect x="49.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7114- <polygon transform="matrix(1.091 0 0 .9982 -3.283 .098)" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.916" points="36 12 19 21.75 53 21.75"/>
7115- <rect x="40.35" y="25.61" width="4.298" height="26.3" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.702"/>
7116- </g>
7117- </svg>
7118 diff --git a/ayllu/themes/default/assets/chat.svg b/ayllu/themes/default/assets/chat.svg
7119deleted file mode 100644
7120index e36c2f6..0000000
7121--- a/ayllu/themes/default/assets/chat.svg
7122+++ /dev/null
7123 @@ -1,16 +0,0 @@
7124- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7125- <g id="color">
7126- <path fill="#D0CFCE" stroke="none" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527 l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
7127- <path fill="#9B9B9A" stroke="none" d="M60.5096,60.5444c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077 c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351 L60.5096,60.5444z"/>
7128- </g>
7129- <g id="hair"/>
7130- <g id="skin"/>
7131- <g id="skin-shadow"/>
7132- <g id="line">
7133- <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M15.042,14.9756c-2.2091,0-4,1.7909-4,4v11.7841 l-0.0019,4.5314c-0.0002,0.2808,0.328,0.4336,0.5428,0.2527l5.0257-4.389c0.1804-0.1518,0.4086-0.2351,0.6443-0.2351h30.9557 c2.2091,0,4-1.7909,4-4v-7.9441c0-2.2091-1.7909-4-4-4H15.042z"/>
7134- <path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M60.5096,60.5444 c0.2147,0.1808,0.5427,0.0281,0.5426-0.2526l-0.0018-4.2707h0.0007V44.077c0-2.2091-1.7909-4-4-4H24.103c-2.2091,0-4,1.7909-4,4 v7.9441c0,2.2091,1.7909,4,4,4h30.8579c0.2357,0,0.4639,0.0833,0.6441,0.2351L60.5096,60.5444z"/>
7135- <circle cx="31.9965" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7136- <circle cx="40.0081" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7137- <circle cx="48.0198" cy="48.0593" r="2" fill="#000000" stroke="none"/>
7138- </g>
7139- </svg>
7140 diff --git a/ayllu/themes/default/assets/check.svg b/ayllu/themes/default/assets/check.svg
7141deleted file mode 100644
7142index 631b6ab..0000000
7143--- a/ayllu/themes/default/assets/check.svg
7144+++ /dev/null
7145 @@ -1,8 +0,0 @@
7146- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7147- <g id="color">
7148- <path fill="#b1cc33" d="m61.5 23.3-8.013-8.013-25.71 25.71-9.26-9.26-8.013 8.013 17.42 17.44z"/>
7149- </g>
7150- <g id="line">
7151- <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="m10.5 39.76 17.42 17.44 33.58-33.89-8.013-8.013-25.71 25.71-9.26-9.26z"/>
7152- </g>
7153- </svg>
7154 diff --git a/ayllu/themes/default/assets/code.svg b/ayllu/themes/default/assets/code.svg
7155deleted file mode 100644
7156index f92f207..0000000
7157--- a/ayllu/themes/default/assets/code.svg
7158+++ /dev/null
7159 @@ -1,17 +0,0 @@
7160- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7161- <g id="color">
7162- <rect x="11" y="16.0833" width="50" height="39.8333" fill="#d0cfce" stroke="none"/>
7163- </g>
7164- <g id="hair"/>
7165- <g id="skin"/>
7166- <g id="skin-shadow"/>
7167- <g id="line">
7168- <rect x="11" y="16.0009" width="50" height="39.9982" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7169- <polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="16.3287,16.4792 16.3287,20.8542 11,20.8542 61,20.8542"/>
7170- <line x1="28.8333" x2="21.9062" y1="30.3947" y2="37.3218" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7171- <line x1="28.8333" x2="21.9062" y1="44.3166" y2="37.3895" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7172- <line x1="38.1836" x2="32.8086" y1="28.1523" y2="46.25" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7173- <line x1="42.1588" x2="49.0859" y1="44.2515" y2="37.3244" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7174- <line x1="42.1588" x2="49.0859" y1="30.3296" y2="37.2567" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7175- </g>
7176- </svg>
7177 diff --git a/ayllu/themes/default/assets/favicon.ico b/ayllu/themes/default/assets/favicon.ico
7178deleted file mode 100644
7179index 613ab78..0000000
7180 Binary files a/ayllu/themes/default/assets/favicon.ico and /dev/null differ
7181 diff --git a/ayllu/themes/default/assets/feed.svg b/ayllu/themes/default/assets/feed.svg
7182deleted file mode 100644
7183index 2fd8aa4..0000000
7184--- a/ayllu/themes/default/assets/feed.svg
7185+++ /dev/null
7186 @@ -1,14 +0,0 @@
7187- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7188- <g id="color">
7189- <path fill="#F1B31C" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60Z"/>
7190- <circle cx="24.5" cy="47.5" r="4.5" fill="#ffffff"/>
7191- <path fill="#ffffff" fill-rule="evenodd" d="M42 52C42 39.8497 32.1503 30 20 30V37C28.2843 37 35 43.7157 35 52H42Z" clip-rule="evenodd"/>
7192- <path fill="#ffffff" fill-rule="evenodd" d="M52 52C52 34.3269 37.6731 20 20 20V27C33.8071 27 45 38.1929 45 52H52Z" clip-rule="evenodd"/>
7193- </g>
7194- <g id="line">
7195- <path fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59.0349 60H12.9649C12.7092 59.9992 12.4642 59.8973 12.2834 59.7164C12.1026 59.5356 12.0007 59.2906 12 59.0349V12.9649C12.0008 12.7092 12.1027 12.4642 12.2836 12.2834C12.4644 12.1026 12.7094 12.0007 12.9651 12H59.0351C59.2908 12.0008 59.5358 12.1027 59.7166 12.2836C59.8974 12.4644 59.9993 12.7094 60 12.9651V59.0351C59.9992 59.2908 59.8973 59.5358 59.7164 59.7166C59.5356 59.8974 59.2906 59.9993 59.0349 60V60Z"/>
7196- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M29 47.5C29 49.9853 26.9853 52 24.5 52C22.0147 52 20 49.9853 20 47.5C20 45.0147 22.0147 43 24.5 43C26.9853 43 29 45.0147 29 47.5Z"/>
7197- <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M41 52C41 40.402 31.598 31 20 31V37C28.2843 37 35 43.7157 35 52H41Z" clip-rule="evenodd"/>
7198- <path fill="none" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="2" d="M52 52C52 34.3269 37.6731 20 20 20V26.0012C34.1062 26.1354 45.5 37.6121 45.5 51.75C45.5 51.8334 45.4996 51.9168 45.4988 52H52Z" clip-rule="evenodd"/>
7199- </g>
7200- </svg>
7201 diff --git a/ayllu/themes/default/assets/feed.xsl b/ayllu/themes/default/assets/feed.xsl
7202deleted file mode 100644
7203index ff34fab..0000000
7204--- a/ayllu/themes/default/assets/feed.xsl
7205+++ /dev/null
7206 @@ -1,56 +0,0 @@
7207- <?xml version="1.0" encoding="utf-8"?>
7208- <xsl:stylesheet version="3.0"
7209- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
7210- xmlns:atom="http://www.w3.org/2005/Atom"
7211- xmlns:dc="http://purl.org/dc/elements/1.1/"
7212- xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
7213- <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
7214- <xsl:template match="/">
7215- <html lang="en">
7216- <head>
7217- <title>
7218- <xsl:value-of select="/rss/channel/title"/>
7219- </title>
7220- <link rel="stylesheet" href="/static/main.min.css" />
7221- </head>
7222- <body>
7223- <main class="column">
7224- <article>
7225- <header>
7226- <h1>
7227- <img class="rss-icon-feed" src="/static/feed.svg"/>
7228- <a>
7229- <xsl:attribute name="href"> <xsl:value-of select="/rss/channel/link"/> </xsl:attribute>
7230- <xsl:value-of select="/rss/channel/title"/>
7231- </a>
7232- </h1>
7233- <h2><xsl:value-of select="/rss/channel/description"/></h2>
7234- </header>
7235- <p><strong>This is an <a href="https://en.wikipedia.org/wiki/RSS">RSS feed</a>.</strong></p>
7236- <p>Copy the link from the address bar into your feed reader to receive regular updates.</p>
7237- </article>
7238- <xsl:for-each select="/rss/channel/item">
7239- <article class="card">
7240- <header>
7241- <h3>
7242- <a>
7243- <xsl:attribute name="href">
7244- <xsl:value-of select="link"/>
7245- </xsl:attribute>
7246- <xsl:value-of select="title"/>
7247- </a>
7248- </h3>
7249- </header>
7250- <xsl:value-of select="description" disable-output-escaping="yes"/>
7251- <footer class="rss">
7252- <xsl:value-of select="author" /><br/>
7253- <b><xsl:value-of select="pubDate"/></b>
7254- </footer>
7255- </article>
7256- </xsl:for-each>
7257- </main>
7258- </body>
7259- </html>
7260- </xsl:template>
7261- </xsl:stylesheet>
7262-
7263 diff --git a/ayllu/themes/default/assets/logo.svg b/ayllu/themes/default/assets/logo.svg
7264deleted file mode 100644
7265index d1db83a..0000000
7266--- a/ayllu/themes/default/assets/logo.svg
7267+++ /dev/null
7268 @@ -1,101 +0,0 @@
7269- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
7270- <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" version="1.1" viewBox="0 0 902 702">
7271- <metadata>
7272- <rdf:RDF>
7273- <cc:Work rdf:about="">
7274- <dc:format>image/svg+xml</dc:format>
7275- <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
7276- </cc:Work>
7277- </rdf:RDF>
7278- </metadata>
7279- <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
7280- <g fill-rule="evenodd" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-opacity="1">
7281- <g id="brown" fill="#5c451c" stroke="#5c451c">
7282- <path style="" d="M 1,1 H 901 V 701 H 1 Z m 0,0"/>
7283- </g>
7284- <g id="orange" fill="#ffa300" stroke="#ffa300">
7285- <path d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7286- <path d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7287- <path d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7288- <path d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7289- </g>
7290- <g id="turquoise" fill="#358794" stroke="#358794">
7291- <path d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7292- <path d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7293- <path d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7294- <path d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7295- <path d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7296- <path d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7297- <path d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7298- <path d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7299- <path d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7300- <path d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7301- <path d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7302- <path d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7303- </g>
7304- <g id="white" fill="#ffffff" stroke="#ffffff">
7305- <path d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7306- <path d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7307- <path d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7308- <path d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7309- <path d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7310- <path d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7311- <path d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7312- <path d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7313- <path d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7314- <path d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7315- <path d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7316- <path d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7317- <path d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0"/>
7318- <path d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0"/>
7319- <path d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0"/>
7320- <path d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0"/>
7321- <path d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0"/>
7322- <path d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0"/>
7323- <path d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0"/>
7324- <path d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0"/>
7325- <path d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7326- <path d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7327- <path d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7328- <path d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7329- <path d="M 1,21 H 881 V 61 H 1 Z m 0,0"/>
7330- <path d="m 1,641 h 880 v 40 H 1 Z m 0,0"/>
7331- <path d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7332- <path d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7333- <path d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7334- <path d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0"/>
7335- <path d="m 401,301 h 20 v 20 h -20 z m 0,0"/>
7336- <path d="m 381,321 h 20 v 20 h -20 z m 0,0"/>
7337- <path d="m 361,341 h 20 v 20 h -20 z m 0,0"/>
7338- <path d="m 381,361 h 20 v 20 h -20 z m 0,0"/>
7339- <path d="m 401,381 h 20 v 20 h -20 z m 0,0"/>
7340- <path d="m 501,321 h 20 v 20 h -20 z m 0,0"/>
7341- <path d="m 521,341 h 20 v 20 h -20 z m 0,0"/>
7342- <path d="m 501,361 h 20 v 20 h -20 z m 0,0"/>
7343- <path d="m 481,381 h 20 v 20 h -20 z m 0,0"/>
7344- <path d="m 421,341 h 20 v 20 h -20 z m 0,0"/>
7345- <path d="m 461,341 h 20 v 20 h -20 z m 0,0"/>
7346- <path d="m 881,21 h 20 v 40 h -20 z m 0,0"/>
7347- <path d="m 881,641 h 20 v 40 h -20 z m 0,0"/>
7348- </g>
7349- <g id="red" fill="#de3333" stroke="#de3333">
7350- <path d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0"/>
7351- <path d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0"/>
7352- <path d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0"/>
7353- <path d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0"/>
7354- <path d="m 381,341 h 20 v 20 h -20 z m 0,0"/>
7355- <path d="m 401,321 h 20 v 20 h -20 z m 0,0"/>
7356- <path d="m 421,301 h 20 v 20 h -20 z m 0,0"/>
7357- <path d="m 461,301 h 20 v 20 h -20 z m 0,0"/>
7358- <path d="m 481,321 h 20 v 20 h -20 z m 0,0"/>
7359- <path d="m 501,341 h 20 v 20 h -20 z m 0,0"/>
7360- <path d="m 481,361 h 20 v 20 h -20 z m 0,0"/>
7361- <path d="m 461,381 h 20 v 20 h -20 z m 0,0"/>
7362- <path d="m 441,361 h 20 v 20 h -20 z m 0,0"/>
7363- <path d="m 421,381 h 20 v 20 h -20 z m 0,0"/>
7364- <path d="m 401,361 h 20 v 20 h -20 z m 0,0"/>
7365- <path d="m 881,81 h 20 v 220 h -20 z m 0,0"/>
7366- <path d="m 881,401 h 20 v 220 h -20 z m 0,0"/>
7367- </g>
7368- </g>
7369- </svg>
7370 diff --git a/ayllu/themes/default/assets/question.svg b/ayllu/themes/default/assets/question.svg
7371deleted file mode 100644
7372index 9ee75dd..0000000
7373--- a/ayllu/themes/default/assets/question.svg
7374+++ /dev/null
7375 @@ -1,13 +0,0 @@
7376- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7377- <g id="color">
7378- <path fill="#fff" stroke-miterlimit="10" d="m48.72 20.15c0.3051 9.298-8.021 12.75-10.82 19.21v7.565c0 1.39-1.11 2.5-2.5 2.5-1.38 0-2.5-1.11-2.5-2.5v-9.865c3.493-6.142 10.38-7.469 10.67-17.06 0-5.66-6.543-6.151-7.988-6.175h-0.05c-5.767 1.187-6.135 4.99-7.289 9.549-0.6671 1.521-1.564 2.045-2.9 2.03-1.36-0.2401-2.26-1.54-2.02-2.9 0.5877-2.752 0.758-6.639 2.35-8.502 2.71-3.14 4.819-4.957 9.179-5.167 0.24 0 0.49-0.01 0.73-0.01 0.09 0 0.18 0 0.27 0.01 8.27 0.2401 12.6 2.992 12.87 11.32z"/>
7379- <circle cx="34.95" cy="58.84" r="3" fill="#fff" stroke-miterlimit="10"/>
7380- </g>
7381- <g id="hair"/>
7382- <g id="skin"/>
7383- <g id="skin-shadow"/>
7384- <g id="line">
7385- <circle cx="35.3" cy="58.84" r="2.625" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.75"/>
7386- <path d="m35.67 10.07c6.57 0 12.03 3.431 12.03 10.21 0 3.671-0.48 6.526-3.71 9.755-3.956 3.956-7.184 6.346-7.208 8.549-0.0651 5.826-5e-3 8.278-0.0044 8.36a1.476 1.476 0 0 1-1.464 1.487h-0.0115a1.476 1.476 0 0 1-1.476-1.464c-5e-4 -0.0819-0.0607-2.561 0.0044-8.416 0.0358-3.231 3.63-6.127 8.385-10.88 2.178-2.174 2.474-4.716 2.474-7.127 0-3.282-2.965-7.587-8.984-7.587-5.338 0-8.499 3.467-8.499 9.552a1.434 1.434 0 0 1-1.395 1.587 1.552 1.552 0 0 1-1.505-1.759c0-9.882 7.291-12.27 11.36-12.27m0-2c-4.964 0-13.36 3.005-13.36 14.27a3.519 3.519 0 0 0 3.505 3.759 3.418 3.418 0 0 0 3.395-3.587c0-3.445 1.128-7.552 6.499-7.552 4.817 0 6.984 3.267 6.984 5.587 0 2.364-0.3155 4.144-1.886 5.712-0.7515 0.75-1.474 1.454-2.157 2.12-3.933 3.833-6.775 6.602-6.815 10.15-0.055 4.946-0.0218 7.539-5e-3 8.424a3.476 3.476 0 1 0 6.952-0.0248l-0.0012-0.075c-0.0164-0.8595-0.0488-3.384 0.0056-8.248 0.0092-0.8384 2.102-2.822 3.948-4.572 0.8091-0.767 1.726-1.636 2.675-2.585 3.577-3.577 4.296-6.926 4.296-11.17 0-7.301-5.638-12.21-14.03-12.21z"/>
7387- </g>
7388- </svg>
7389 diff --git a/ayllu/themes/default/assets/rss.svg b/ayllu/themes/default/assets/rss.svg
7390deleted file mode 100644
7391index b325149..0000000
7392--- a/ayllu/themes/default/assets/rss.svg
7393+++ /dev/null
7394 @@ -1,18 +0,0 @@
7395- <?xml version="1.0"?>
7396- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
7397- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256">
7398- <defs>
7399- <linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
7400- <stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/>
7401- <stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/>
7402- <stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/>
7403- <stop offset="1.0" stop-color="#D95B29"/>
7404- </linearGradient>
7405- </defs>
7406- <rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/>
7407- <rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/>
7408- <rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/>
7409- <circle cx="68" cy="189" r="24" fill="#FFF"/>
7410- <path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
7411- <path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
7412- </svg>
7413 diff --git a/ayllu/themes/default/assets/scale.svg b/ayllu/themes/default/assets/scale.svg
7414deleted file mode 100644
7415index 8ffe8c3..0000000
7416--- a/ayllu/themes/default/assets/scale.svg
7417+++ /dev/null
7418 @@ -1,22 +0,0 @@
7419- <svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
7420- <g id="color">
7421- <path fill="#9B9B9A" stroke="none" d="M48.0626,62.92c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6H48.0626z"/>
7422- <path fill="#D0CFCE" stroke="none" d="M24.9006,46.9656c0,2.1938-2.1985,3.9723-4.9106,3.9723s-4.9106-1.7785-4.9106-3.9723H24.9006z"/>
7423- <path fill="#D0CFCE" stroke="none" d="M57.9006,46.9248c0,2.1938-2.1985,3.9723-4.9106,3.9723c-2.712,0-4.9106-1.7785-4.9106-3.9723H57.9006z"/>
7424- </g>
7425- <g id="hair"/>
7426- <g id="skin"/>
7427- <g id="skin-shadow"/>
7428- <g id="line">
7429- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M15.9996,25.1697c2.6667,0.0001,8.9544-5.3333,20.0002-5.3333S50.6666,23.8365,56,25.1697"/>
7430- <line x1="35.9998" x2="35.9998" y1="23.9064" y2="51.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7431- <circle cx="35.9998" cy="13.895" r="3" fill="#000000" stroke="none"/>
7432- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M48,61.9064c0-3.3137-5.5964-6-12.5-6s-12.5,2.6863-12.5,6"/>
7433- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M26,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H26z"/>
7434- <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="20,27.9064 15,45.9064 25,45.9064"/>
7435- <line x1="20" x2="20" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7436- <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M59,45.9064c0,3.3137-2.6863,6-6,6s-6-2.6863-6-6H59z"/>
7437- <polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="53,27.9064 48,45.9064 58,45.9064"/>
7438- <line x1="53" x2="53" y1="27.9064" y2="45.9064" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
7439- </g>
7440- </svg>
7441 diff --git a/ayllu/themes/default/assets/textile-pattern-1.svg b/ayllu/themes/default/assets/textile-pattern-1.svg
7442deleted file mode 100644
7443index 28124d3..0000000
7444--- a/ayllu/themes/default/assets/textile-pattern-1.svg
7445+++ /dev/null
7446 @@ -1,90 +0,0 @@
7447- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
7448- <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (1.0+r73+1)" sodipodi:docname="textil-inca-pattern-3a.svg" id="svg159" version="1.1" viewBox="0 0 902 702">
7449- <metadata id="metadata165">
7450- <rdf:RDF>
7451- <cc:Work rdf:about="">
7452- <dc:format>image/svg+xml</dc:format>
7453- <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
7454- </cc:Work>
7455- </rdf:RDF>
7456- </metadata>
7457- <defs id="defs163"/>
7458- <sodipodi:namedview inkscape:current-layer="svg159" inkscape:window-maximized="1" inkscape:window-y="27" inkscape:window-x="67" inkscape:cy="467.33333" inkscape:cx="600.66667" inkscape:zoom="0.75746799" showgrid="false" id="namedview161" inkscape:window-height="1025" inkscape:window-width="1853" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff"/>
7459- <path style="fill:#5c451c;fill-opacity:1;fill-rule:evenodd;stroke:#5c451c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,1 H 901 V 701 H 1 Z m 0,0" id="path4"/>
7460- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path6"/>
7461- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 121,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path8"/>
7462- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path10"/>
7463- <path style="fill:#ffa300;fill-opacity:1;fill-rule:evenodd;stroke:#ffa300;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path12"/>
7464- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,221 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path14"/>
7465- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,481 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path16"/>
7466- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path18"/>
7467- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,341 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path20"/>
7468- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,221 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path22"/>
7469- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,481 H 181 V 461 H 161 V 441 H 141 V 421 H 121 V 401 H 101 V 381 H 81 V 361 H 61 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path24"/>
7470- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path26"/>
7471- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,341 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path28"/>
7472- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,121 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path30"/>
7473- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 261,361 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path32"/>
7474- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,121 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path34"/>
7475- <path style="fill:#358794;fill-opacity:1;fill-rule:evenodd;stroke:#358794;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 641,361 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path36"/>
7476- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,161 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path38"/>
7477- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,541 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path40"/>
7478- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,61 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path42"/>
7479- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,641 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path44"/>
7480- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,61 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path46"/>
7481- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,421 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 40 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path48"/>
7482- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 621,281 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -40 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path50"/>
7483- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,281 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path52"/>
7484- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,161 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 H 81 v 20 H 61 v 20 H 41 v 20 H 21 v 20 H 1 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path54"/>
7485- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,541 H 181 V 521 H 161 V 501 H 141 V 481 H 121 V 461 H 101 V 441 H 81 V 421 H 61 V 401 H 41 V 381 H 21 V 361 H 1 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path56"/>
7486- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,641 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -40 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path58"/>
7487- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 281,421 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 40 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path60"/>
7488- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 701 Z m 0,0" id="path62"/>
7489- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 721,181 H 701 V 101 H 601 v 40 h 40 v -20 h 20 v 40 H 581 V 81 h 140 z m 0,0" id="path64"/>
7490- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 701,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 701 Z m 0,0" id="path66"/>
7491- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 721,521 h -20 v 80 H 601 v -40 h 40 v 20 h 20 v -40 h -80 v 80 h 140 z m 0,0" id="path68"/>
7492- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,521 h 20 v 80 h 100 v -40 h -40 v 20 h -20 v -40 h 80 v 80 H 181 Z m 0,0" id="path70"/>
7493- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 201,521 h -20 v 80 H 81 v -40 h 40 v 20 h 20 V 541 H 61 v 80 h 140 z m 0,0" id="path72"/>
7494- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 181,181 h 20 v -80 h 100 v 40 h -40 v -20 h -20 v 40 h 80 V 81 H 181 Z m 0,0" id="path74"/>
7495- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 201,181 H 181 V 101 H 81 v 40 h 40 v -20 h 20 v 40 H 61 V 81 h 140 z m 0,0" id="path76"/>
7496- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,181 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h -40 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path78"/>
7497- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,181 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h 40 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path80"/>
7498- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,521 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h -40 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path82"/>
7499- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,521 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h 40 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path84"/>
7500- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 1,21 H 881 V 61 H 1 Z m 0,0" id="path86"/>
7501- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 1,641 h 880 v 40 H 1 Z m 0,0" id="path88"/>
7502- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,281 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path90"/>
7503- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 681,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path92"/>
7504- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 161,341 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path94"/>
7505- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,401 h 20 v -20 h 20 v 20 h 20 v 20 h -20 v 20 h -20 v -20 h -20 z m 0,0" id="path96"/>
7506- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,301 h 20 v 20 h -20 z m 0,0" id="path98"/>
7507- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,321 h 20 v 20 h -20 z m 0,0" id="path100"/>
7508- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 361,341 h 20 v 20 h -20 z m 0,0" id="path102"/>
7509- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,361 h 20 v 20 h -20 z m 0,0" id="path104"/>
7510- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,381 h 20 v 20 h -20 z m 0,0" id="path106"/>
7511- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,321 h 20 v 20 h -20 z m 0,0" id="path108"/>
7512- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 521,341 h 20 v 20 h -20 z m 0,0" id="path110"/>
7513- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,361 h 20 v 20 h -20 z m 0,0" id="path112"/>
7514- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,381 h 20 v 20 h -20 z m 0,0" id="path114"/>
7515- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,341 h 20 v 20 h -20 z m 0,0" id="path116"/>
7516- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,341 h 20 v 20 h -20 z m 0,0" id="path118"/>
7517- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,21 h 20 v 40 h -20 z m 0,0" id="path120"/>
7518- <path style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,641 h 20 v 40 h -20 z m 0,0" id="path122"/>
7519- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,201 h 60 V 81 h 40 v 220 h -20 v -20 h -20 v -20 h -20 v -20 h -20 v -20 h -20 z m 0,0" id="path124"/>
7520- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,201 H 41 V 81 H 1 v 220 h 20 v -20 h 20 v -20 h 20 v -20 h 20 v -20 h 20 z m 0,0" id="path126"/>
7521- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="M 101,501 H 41 V 621 H 1 V 401 h 20 v 20 h 20 v 20 h 20 v 20 h 20 v 20 h 20 z m 0,0" id="path128"/>
7522- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 801,501 h 60 v 120 h 40 V 401 h -20 v 20 h -20 v 20 h -20 v 20 h -20 v 20 h -20 z m 0,0" id="path130"/>
7523- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 381,341 h 20 v 20 h -20 z m 0,0" id="path132"/>
7524- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,321 h 20 v 20 h -20 z m 0,0" id="path134"/>
7525- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,301 h 20 v 20 h -20 z m 0,0" id="path136"/>
7526- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,301 h 20 v 20 h -20 z m 0,0" id="path138"/>
7527- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,321 h 20 v 20 h -20 z m 0,0" id="path140"/>
7528- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 501,341 h 20 v 20 h -20 z m 0,0" id="path142"/>
7529- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 481,361 h 20 v 20 h -20 z m 0,0" id="path144"/>
7530- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 461,381 h 20 v 20 h -20 z m 0,0" id="path146"/>
7531- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 441,361 h 20 v 20 h -20 z m 0,0" id="path148"/>
7532- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 421,381 h 20 v 20 h -20 z m 0,0" id="path150"/>
7533- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 401,361 h 20 v 20 h -20 z m 0,0" id="path152"/>
7534- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,81 h 20 v 220 h -20 z m 0,0" id="path154"/>
7535- <path style="fill:#de3333;fill-opacity:1;fill-rule:evenodd;stroke:#de3333;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" d="m 881,401 h 20 v 220 h -20 z m 0,0" id="path156"/>
7536- </svg>
7537 diff --git a/ayllu/themes/default/assets/textile-pattern-2.svg b/ayllu/themes/default/assets/textile-pattern-2.svg
7538deleted file mode 100644
7539index f442f7a..0000000
7540--- a/ayllu/themes/default/assets/textile-pattern-2.svg
7541+++ /dev/null
7542 @@ -1,308 +0,0 @@
7543- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
7544- <svg
7545- xmlns:dc="http://purl.org/dc/elements/1.1/"
7546- xmlns:cc="http://creativecommons.org/ns#"
7547- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7548- xmlns:svg="http://www.w3.org/2000/svg"
7549- xmlns="http://www.w3.org/2000/svg"
7550- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
7551- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
7552- width="1441.0001pt"
7553- height="1441pt"
7554- viewBox="0 0 1441.0001 1441"
7555- version="1.1"
7556- id="svg11"
7557- sodipodi:docname="Manto-funerario-paracas-2.svg"
7558- inkscape:version="1.0 (1.0+r73+1)">
7559- <metadata
7560- id="metadata17">
7561- <rdf:RDF>
7562- <cc:Work
7563- rdf:about="">
7564- <dc:format>image/svg+xml</dc:format>
7565- <dc:type
7566- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
7567- <dc:title />
7568- </cc:Work>
7569- </rdf:RDF>
7570- </metadata>
7571- <defs
7572- id="defs15" />
7573- <sodipodi:namedview
7574- inkscape:document-rotation="0"
7575- pagecolor="#ffffff"
7576- bordercolor="#666666"
7577- borderopacity="1"
7578- objecttolerance="10"
7579- gridtolerance="10"
7580- guidetolerance="10"
7581- inkscape:pageopacity="0"
7582- inkscape:pageshadow="2"
7583- inkscape:window-width="1853"
7584- inkscape:window-height="1025"
7585- id="namedview13"
7586- showgrid="false"
7587- inkscape:zoom="0.18438366"
7588- inkscape:cx="471.3773"
7589- inkscape:cy="745.57387"
7590- inkscape:window-x="67"
7591- inkscape:window-y="27"
7592- inkscape:window-maximized="1"
7593- inkscape:current-layer="svg11" />
7594- <g
7595- transform="translate(-0.49999149,719.5)"
7596- id="g921">
7597- <path
7598- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7599- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
7600- id="path4" />
7601- <path
7602- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7603- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
7604- id="path6" />
7605- <path
7606- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7607- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
7608- id="path8" />
7609- </g>
7610- <g
7611- id="g895"
7612- transform="translate(384.03529,784.594)">
7613- <path
7614- id="path4-2"
7615- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7616- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7617- <path
7618- id="path6-9"
7619- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7620- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7621- <path
7622- id="path8-1"
7623- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7624- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7625- </g>
7626- <g
7627- id="g944"
7628- transform="translate(-198.02416,1142.587)">
7629- <path
7630- id="path4-27"
7631- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
7632- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7633- <path
7634- id="path6-0"
7635- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
7636- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7637- <path
7638- id="path8-9"
7639- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
7640- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7641- </g>
7642- <g
7643- transform="translate(744.03529,424.59399)"
7644- id="g895-3">
7645- <path
7646- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7647- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7648- id="path4-2-6" />
7649- <path
7650- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7651- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7652- id="path6-9-0" />
7653- <path
7654- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7655- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7656- id="path8-1-6" />
7657- </g>
7658- <g
7659- id="g921-2"
7660- transform="translate(359.50001,359.5)">
7661- <path
7662- id="path4-6"
7663- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
7664- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7665- <path
7666- id="path6-1"
7667- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
7668- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7669- <path
7670- id="path8-8"
7671- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
7672- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7673- </g>
7674- <g
7675- id="g921-9"
7676- transform="translate(719.50001,-0.5)">
7677- <path
7678- id="path4-7"
7679- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
7680- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7681- <path
7682- id="path6-3"
7683- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
7684- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7685- <path
7686- id="path8-6"
7687- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
7688- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7689- </g>
7690- <g
7691- transform="translate(1104.0353,64.594)"
7692- id="g895-1">
7693- <path
7694- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7695- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7696- id="path4-2-2" />
7697- <path
7698- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7699- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7700- id="path6-9-9" />
7701- <path
7702- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7703- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7704- id="path8-1-3" />
7705- </g>
7706- <g
7707- transform="translate(521.97583,422.58699)"
7708- id="g944-1">
7709- <path
7710- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7711- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
7712- id="path4-27-9" />
7713- <path
7714- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7715- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
7716- id="path6-0-4" />
7717- <path
7718- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7719- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
7720- id="path8-9-7" />
7721- </g>
7722- <g
7723- id="g895-3-8"
7724- transform="translate(1464.0353,-295.406)">
7725- <path
7726- id="path4-2-6-4"
7727- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7728- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7729- <path
7730- id="path6-9-0-5"
7731- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7732- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7733- <path
7734- id="path8-1-6-0"
7735- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7736- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7737- </g>
7738- <g
7739- transform="translate(161.97582,782.587)"
7740- id="g944-3">
7741- <path
7742- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7743- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
7744- id="path4-27-6" />
7745- <path
7746- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7747- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
7748- id="path6-0-1" />
7749- <path
7750- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7751- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
7752- id="path8-9-0" />
7753- </g>
7754- <g
7755- id="g921-6"
7756- transform="translate(719.50003,1079.5)">
7757- <path
7758- id="path4-3"
7759- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
7760- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7761- <path
7762- id="path6-2"
7763- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
7764- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7765- <path
7766- id="path8-0"
7767- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
7768- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7769- </g>
7770- <g
7771- transform="translate(1079.5,719.5)"
7772- id="g921-2-6">
7773- <path
7774- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7775- d="M 1,1 H 361 V 361 H 1 Z m 0,0"
7776- id="path4-6-1" />
7777- <path
7778- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7779- d="m 261,281 h -80 v 50 H 101 V 281 H 21 L 61,201 H 21 L 61,121 H 21 L 81,31 V 231 H 261 V 91 l -50,110 h -80 l -30,-70 v -30 l 50,-70 h 10 v 40 h 20 V 31 h 10 l 30,50 20,-50 h 40 l 60,100 h -60 l 60,100 h -60 l 60,100 h -80 z m 0,0"
7780- id="path6-1-5" />
7781- <path
7782- style="fill:#730033;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7783- d="m 141,131 20,-20 h 20 l 20,20 -20,40 h -20 z m 0,0"
7784- id="path8-8-5" />
7785- </g>
7786- <g
7787- transform="translate(-558.02418,422.58698)"
7788- id="g944-4">
7789- <path
7790- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1"
7791- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
7792- id="path4-27-7" />
7793- <path
7794- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7795- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
7796- id="path6-0-6" />
7797- <path
7798- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1"
7799- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
7800- id="path8-9-5" />
7801- </g>
7802- <g
7803- id="g944-3-6"
7804- transform="translate(-198.02419,62.58699)">
7805- <path
7806- id="path4-27-6-9"
7807- d="M 558.52418,-62.086991 H 918.52419 V 297.91302 H 558.52418 Z m 0,0"
7808- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7809- <path
7810- id="path6-0-1-3"
7811- d="m 818.52419,217.91302 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.000008 h -40 l 60.00001,-90.000003 V 167.91302 h 180 V 27.913013 l -50,110.000007 H 688.52418 L 658.52419,67.913012 v -29.99999 l 50,-70.000013 h 9.99999 V 7.9130143 h 20.00001 V -32.086991 h 9.99999 l 30,50.000006 20,-50.000006 h 40.00001 l 60,100.000003 h -60 l 60,100.000008 h -60 l 60,100 h -80 z m 0,0"
7812- style="fill:#d66236;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7813- <path
7814- id="path8-9-0-7"
7815- d="m 698.52418,67.913012 20,-20 h 20.00001 l 20,20 -20,39.999998 h -20.00001 z m 0,0"
7816- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7817- </g>
7818- <g
7819- id="g895-1-4"
7820- transform="translate(384.03528,-295.406)">
7821- <path
7822- id="path4-2-2-5"
7823- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7824- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7825- <path
7826- id="path6-9-9-2"
7827- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7828- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7829- <path
7830- id="path8-1-3-5"
7831- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7832- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7833- </g>
7834- <g
7835- id="g895-1-47"
7836- transform="translate(1464.0353,784.594)">
7837- <path
7838- id="path4-2-2-4"
7839- d="M -383.53528,295.906 H -23.535268 V 655.90601 H -383.53528 Z m 0,0"
7840- style="fill:#e15031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-opacity:1" />
7841- <path
7842- id="path6-9-9-4"
7843- d="m -123.53527,575.90601 h -80 v 50 h -80 v -50 h -80.00001 l 40,-80 h -40 l 40,-80.00001 h -40 l 60,-90 v 200.00001 h 180.00001 V 385.906 l -50,110.00001 h -80.00001 L -283.53527,425.906 v -29.99999 l 50,-70.00001 h 9.99999 v 40 h 20.00001 v -40 h 9.99999 l 30,50 20,-50 h 40.00001 l 60.000002,100 h -60.000002 l 60.000002,100.00001 h -60.000002 l 60.000002,100 h -80.000002 z m 0,0"
7844- style="fill:#ed2031;fill-opacity:1;fill-rule:evenodd;stroke:#730033;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7845- <path
7846- id="path8-1-3-3"
7847- d="m -243.53528,425.906 20,-20 h 20.00001 l 20,20 -20,40 h -20.00001 z m 0,0"
7848- style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.00002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-opacity:1" />
7849- </g>
7850- </svg>
7851 diff --git a/ayllu/themes/default/assets/xmpp.svg b/ayllu/themes/default/assets/xmpp.svg
7852deleted file mode 100644
7853index 0c904a7..0000000
7854--- a/ayllu/themes/default/assets/xmpp.svg
7855+++ /dev/null
7856 @@ -1,30 +0,0 @@
7857- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
7858- <!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
7859- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
7860- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xml:space="preserve" viewBox="0 0 200 200" width="200px" height="200px" x="0px" y="0px" enable-background="new 0 0 200 200">
7861-
7862- <linearGradient id="SVGID_right_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="translate(1196.604,15.368977)" y1="126.85" x1="-1073.2">
7863- <stop stop-color="#1b3967" offset=".011"/>
7864- <stop stop-color="#13b5ea" offset=".467"/>
7865- <stop stop-color="#002b5c" offset=".9945"/>
7866- </linearGradient>
7867-
7868- <linearGradient id="SVGID_left_" y2="1.279e-13" gradientUnits="userSpaceOnUse" x2="-1073.2" gradientTransform="matrix(-1,0,0,1,-994.78801,15.367977)" y1="126.85" x1="-1073.2">
7869- <stop stop-color="#1b3967" offset=".011"/>
7870- <stop stop-color="#13b5ea" offset=".467"/>
7871- <stop stop-color="#002b5c" offset=".9945"/>
7872- </linearGradient>
7873-
7874- <path d="m 151.80512,29.557978 c 0.077,1.313 -1.787,0.968 -1.787,2.293 0,38.551 -46.558,97.366012 -91.687985,108.730012 v 1.639 C 118.28313,136.69999 186.89012,74.419978 188.40012,15.369977 l -36.599,14.189001 z" style="fill:url(#SVGID_right_)"/>
7875- <path d="m 133.67312,34.300978 c 0.076,1.313 0.12,2.63 0.12,3.957 0,38.551 -30.69898,90.497012 -75.826985,101.860012 v 1.639 c 59.044005,-2.79 105.809995,-63.024012 105.809995,-109.200012 0,-2.375 -0.128,-4.729 -0.371,-7.056 l -29.73,8.798 z" style="fill:#e96d1f"/>
7876- <path d="m 163.69112,24.951978 -7.61699,2.722 c 0.041,0.962 0.066,2.254 0.066,3.225 0,41.219 -37.271,98.204012 -87.271995,107.120012 -3.24501,1.088 -7.53801,2.077 -10.932,2.931 v 1.638 C 123.19013,137.02799 169.03613,70.721978 163.69612,24.947978 Z" style="fill:#d9541e"/>
7877-
7878- <path d="m 50.011,29.556978 c -0.077,1.313 1.787,0.968 1.787,2.293 0,38.551 46.558007,97.366012 91.68799,108.730012 v 1.639 C 83.533,136.69899 14.926,74.418978 13.416,15.368977 l 36.599,14.189001 z" style="fill:url(#SVGID_left_)"/>
7879- <path d="m 68.143,34.299978 c -0.076,1.313 -0.12,2.63 -0.12,3.957 0,38.551 30.698995,90.497012 75.82699,101.860012 v 1.639 C 84.806,138.96599 38.04,78.731978 38.04,32.555978 c 0,-2.375 0.128,-4.729 0.371,-7.056 l 29.73,8.798 z" style="fill:#a0ce67"/>
7880- <path d="m 38.125,24.950978 7.617,2.722 c -0.041,0.962 -0.066,2.254 -0.066,3.225 0,41.219 37.271,98.204012 87.27199,107.120012 3.245,1.088 7.538,2.077 10.932,2.931 v 1.638 C 78.626,137.02699 32.78,70.720978 38.12,24.946978 Z" style="fill:#439639"/>
7881-
7882- <path d="m 25.988,172.07799 -13.388,-14.65 h 11.643 l 9.127,10.268 9.129,-10.268 h 11.643 l -13.387,14.646 14.401,14.728 h -12.09 l -9.697,-10.67 -9.693,10.67 H 11.584 l 14.404,-14.73 z"/>
7883- <path d="m 58.508,157.42799 h 13.836 l 10.183,18.905 10.183,-18.905 h 13.83199 v 29.374 h -8.761983 v -21.096 h -0.08 L 85.893,186.80199 H 79.16 l -11.807,-21.096 h -0.082 v 21.096 h -8.764 v -29.37 z"/>
7884- <path d="m 112.66199,157.42799 h 24.546 c 8.559,0 10.628,4.302 10.628,10.063 v 2.516 c 0,4.381 -1.908,9.41 -8.275,9.41 h -17.894 v 7.385 h -9.005 v -29.38 z m 9,14.69 h 13.997 c 2.10901,0 2.92401,-1.377 2.92401,-3.123 v -1.135 c 0,-1.99 -0.976,-3.127 -3.694,-3.127 h -13.227 v 7.38 z"/>
7885- <path d="m 152.72199,157.42799 h 24.546 c 8.561,0 10.63,4.302 10.63,10.063 v 2.516 c 0,4.381 -1.907,9.41 -8.275,9.41 h -17.893 v 7.385 h -9.008 v -29.38 z m 9.01,14.69 h 13.996 c 2.11,0 2.922,-1.377 2.922,-3.123 v -1.135 c 0,-1.99 -0.974,-3.127 -3.693,-3.127 h -13.225 v 7.38 z"/>
7886- </svg>
7887\ No newline at end of file
7888 diff --git a/ayllu/themes/default/highlight.css b/ayllu/themes/default/highlight.css
7889deleted file mode 100644
7890index bc10de5..0000000
7891--- a/ayllu/themes/default/highlight.css
7892+++ /dev/null
7893 @@ -1,114 +0,0 @@
7894- @import url("../../colors/nord.css");
7895-
7896- @media (prefers-color-scheme: dark) {
7897- table.code {
7898- background-color: var(--nord0);
7899- }
7900-
7901- tr .line {
7902- background-color: var(--nord1);
7903- }
7904-
7905- tr .line-number {
7906- background-color: var(--nord0);
7907- }
7908-
7909- }
7910-
7911-
7912- @media (prefers-color-scheme: dark) {
7913- [data-tooltip]:hover::after {
7914- background-color: var(--nord-6);
7915- }
7916- }
7917-
7918-
7919- /* base syntax highlighting */
7920-
7921- span.ts_attribute {
7922- color: var(--nord14);
7923- }
7924-
7925- span.ts_comment {
7926- color: var(--nord3);
7927- }
7928-
7929- span.ts_constant {
7930- color: var(--nord13);
7931- }
7932-
7933- span.ts_function_builtin {
7934- color: var(--nord9);
7935- }
7936-
7937- span.ts_function {
7938- color: var(--nord8);
7939- }
7940-
7941- span.ts_keyword {
7942- color: var(--nord12);
7943- }
7944-
7945- span.ts_operator {
7946- color: var(--nord9);
7947- }
7948-
7949- span.ts_property {
7950- color: var(--nord11);
7951- }
7952-
7953- span.ts_punctuation {
7954- color: var(--nord5);
7955- }
7956-
7957- span.ts_punctuation_bracket {
7958- color: var(--nord9);
7959- }
7960-
7961- span.ts_punctuation_delimiter {
7962- color: var(--nord10);
7963- }
7964-
7965- span.ts_punctuation_delimiter {
7966- color: var(--nord10);
7967- }
7968-
7969- span.ts_string {
7970- color: var(--nord-14);
7971- }
7972-
7973- span.ts_string_special {
7974- color: var(--nord12);
7975- }
7976-
7977- span.ts_tag {
7978- color: var(--nord7);
7979- }
7980-
7981- span.ts_type {
7982- color: var(--nord7);
7983- }
7984-
7985- span.ts_type_builtin {
7986- color: var(--nord13);
7987- }
7988-
7989- span.ts_variable {
7990- color: var(--nord4);
7991- }
7992-
7993- span.ts_variable_builtin {
7994- color: var(--nord8);
7995- }
7996-
7997- span.ts_variable_parameter {
7998- color: var(--nord8);
7999- }
8000-
8001- span.ts_removal {
8002- color: var(--nord11);
8003- }
8004-
8005- span.ts_addition {
8006- color: var(--nord14);
8007- }
8008 diff --git a/ayllu/themes/default/readme.css b/ayllu/themes/default/readme.css
8009deleted file mode 100644
8010index 7e0a789..0000000
8011--- a/ayllu/themes/default/readme.css
8012+++ /dev/null
8013 @@ -1,43 +0,0 @@
8014- .readme {
8015- margin-top: 1em;
8016- }
8017-
8018- .readme p {
8019- margin: .5em;
8020- }
8021-
8022- .readme :where(img) {
8023- display: revert;
8024- }
8025-
8026- .readme h1,h2,h3,h4,h5,h6 {
8027- margin-bottom: .5em;
8028- margin-top: .5em;
8029- }
8030-
8031- .readme table.code {
8032- font-family: var(--font-mono);
8033- width: 100%;
8034- margin-top: 1em;
8035- text-align: left;
8036- }
8037-
8038- .readme > header {
8039- display: flex;
8040- align-items: center;
8041- }
8042-
8043- .readme > table {
8044- min-width: initial;
8045- }
8046-
8047- .icon-link {
8048- display: flex;
8049- align-items: center;
8050- }
8051-
8052- .icon {
8053- width: 24px;
8054- height: 24px;
8055- fill: currentColor;
8056- }
8057 diff --git a/ayllu/themes/default/templates/404.html b/ayllu/themes/default/templates/404.html
8058deleted file mode 100644
8059index ef22b82..0000000
8060--- a/ayllu/themes/default/templates/404.html
8061+++ /dev/null
8062 @@ -1,9 +0,0 @@
8063- {% extends "base.html" %}
8064- {% block content %}
8065- <section class="stretch">
8066- <h1>Not Found ({{ status_code }})</h1>
8067- <p>Unable to find the resource you have requested, perhaps try looking somewhere else?</p>
8068- <h4>Error Message:</h4>
8069- <p>{{ error_message }}</p>
8070- </section>
8071- {% endblock %}
8072 diff --git a/ayllu/themes/default/templates/5xx.html b/ayllu/themes/default/templates/5xx.html
8073deleted file mode 100644
8074index e0870d3..0000000
8075--- a/ayllu/themes/default/templates/5xx.html
8076+++ /dev/null
8077 @@ -1,9 +0,0 @@
8078- {% extends "base.html" %}
8079- {% block content %}
8080- <section class="strech">
8081- <h1>Internal Service Error ({{ status_code }})</h1>
8082- <p>Something has gone wrong processing your request, please be patient.</p>
8083- <h4>Error Message:</h4>
8084- <p>{{ error_message }}</p>
8085- </section>
8086- {% endblock %}
8087 diff --git a/ayllu/themes/default/templates/about.html b/ayllu/themes/default/templates/about.html
8088deleted file mode 100644
8089index 652e78a..0000000
8090--- a/ayllu/themes/default/templates/about.html
8091+++ /dev/null
8092 @@ -1,6 +0,0 @@
8093- {% extends "base.html" %}
8094- {% block content %}
8095- <section>
8096- <article>{{ blurb | safe }}</article>
8097- </section>
8098- {% endblock %}
8099 diff --git a/ayllu/themes/default/templates/authors.html b/ayllu/themes/default/templates/authors.html
8100deleted file mode 100644
8101index 1fe63c5..0000000
8102--- a/ayllu/themes/default/templates/authors.html
8103+++ /dev/null
8104 @@ -1,20 +0,0 @@
8105- {% extends "base.html" %}
8106- {% block content %}
8107- <header>
8108- <h1>Authors</h1>
8109- </header>
8110- {% for author in authors %}
8111- <article>
8112- <div class="wide">
8113- <div>
8114- <a href="/{{ collection }}/{{ name }}/log?username={{ author.username | urlencode }}&email={{ author.email | urlencode }}">{{ author.username }}</a>
8115- </div>
8116- <div>
8117- <span class="positive">+{{ author.lines_added }}</span>
8118- <span class="negative">-{{ author.lines_removed }}</span>
8119- {{ author.percentage }}% ({{ author.count }})
8120- </div>
8121- </div>
8122- </article>
8123- {% endfor %}
8124- {% endblock %}
8125 diff --git a/ayllu/themes/default/templates/badge.svg b/ayllu/themes/default/templates/badge.svg
8126deleted file mode 100644
8127index 895794b..0000000
8128--- a/ayllu/themes/default/templates/badge.svg
8129+++ /dev/null
8130 @@ -1,29 +0,0 @@
8131- <svg xmlns="http://www.w3.org/2000/svg"
8132- xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20"
8133- role="img" aria-label="{{key}}: {{value}}">
8134- <title>{{key}}: {{value}}</title>
8135- <linearGradient id="s" x2="0" y2="100%">
8136- <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
8137- <stop offset="1" stop-opacity=".1" />
8138- </linearGradient>
8139- <clipPath id="r">
8140- <rect width="108" height="20" rx="3" fill="#fff" />
8141- </clipPath>
8142- <g clip-path="url(#r)">
8143- <rect width="55" height="20" fill="#555" />
8144- <rect x="55" width="53" height="20" fill="#007ec6" />
8145- <rect width="108" height="20" fill="url(#s)" />
8146- </g>
8147- <g fill="#fff" text-anchor="middle"
8148- font-family="monospace"
8149- text-rendering="geometricPrecision" font-size="110">
8150- <text aria-hidden="true" x="285" y="150" fill="#010101"
8151- fill-opacity=".3" transform="scale(.1)" textLength="450">{{key}}</text>
8152- <text x="285" y="140" transform="scale(.1)" fill="#fff"
8153- textLength="450">{{key}}</text>
8154- <text aria-hidden="true" x="805" y="150" fill="#010101"
8155- fill-opacity=".3" transform="scale(.1)" textLength="430">{{value}}</text>
8156- <text x="805" y="140" transform="scale(.1)" fill="#fff"
8157- textLength="430">{{value}}</text>
8158- </g>
8159- </svg>
8160 diff --git a/ayllu/themes/default/templates/base.html b/ayllu/themes/default/templates/base.html
8161deleted file mode 100644
8162index b71fcc3..0000000
8163--- a/ayllu/themes/default/templates/base.html
8164+++ /dev/null
8165 @@ -1,30 +0,0 @@
8166- <!DOCTYPE html>
8167- <html lang="en">
8168- <meta name="description" content="{{ description }}">
8169- <meta name="keywords" content="{{ keywords }}">
8170- <head>
8171- <meta charset="utf-8">
8172- <meta name="viewport" content="width=device-width, initial-scale=1">
8173- <title>
8174- {% block title %}{{ title }}{% endblock %}
8175- </title>
8176- <link rel="stylesheet" href="/static/main.min.css" />
8177- <link href="/static/logo.svg" rel="icon" type="image/svg+xml" />
8178- {% block head %}{% endblock %}
8179- </head>
8180- <body>
8181- <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1404468 -->
8182- <script>0</script>
8183- {% block navigation %}
8184- {% include "nav.html" %}
8185- {% endblock %}
8186- <main>
8187- <div class="container" style="max-width: {{ container_max_width }}">
8188- {% block content %}{% endblock %}
8189- </div>
8190- </main>
8191- <footer>
8192- Rendered {%- if render_time %}in ({{ render_time }} ms){%- endif %} @ {{ current_time }}
8193- </footer>
8194- </body>
8195- </html>
8196 diff --git a/ayllu/themes/default/templates/blame.html b/ayllu/themes/default/templates/blame.html
8197deleted file mode 100644
8198index 7c80977..0000000
8199--- a/ayllu/themes/default/templates/blame.html
8200+++ /dev/null
8201 @@ -1,38 +0,0 @@
8202- {% import "macros.html" as macros %}
8203- {% extends "base.html" %}
8204- {% block content %}
8205- <section class="stretch">
8206- <header>
8207- {{ macros::navigation(items=subnav_elements, title="Blame") }}
8208- </header>
8209- <section class="blame">
8210- {% if is_renderable %}
8211- <article class="blame-left">
8212- <table class="code">
8213- <tbody>
8214- {%- for line in blame_lines -%}
8215- {%- set n_lines = line.end - line.start -%}
8216- <tr>
8217- <td>
8218- {{ line.author_name }} <a href="/{{ collection }}/{{ name }}/commit/{{ line.commit_id }}">{{ line.timestamp }}</a>
8219- </td>
8220- {%- for _ in range(end=n_lines-1) -%}
8221- <tr>
8222- <td>&nbsp;</td>
8223- <td></td>
8224- </tr>
8225- {%- endfor -%}
8226- </tr>
8227- {% endfor %}
8228- </tbody>
8229- </table>
8230- </article>
8231- <article class="blame-right">{{ content.1 | safe }}</article>
8232- {% else %}
8233- <article>
8234- <h2>This content is not blameable</h2>
8235- </article>
8236- {% endif %}
8237- </section>
8238- </section>
8239- {% endblock %}
8240 diff --git a/ayllu/themes/default/templates/blob.html b/ayllu/themes/default/templates/blob.html
8241deleted file mode 100644
8242index e880df9..0000000
8243--- a/ayllu/themes/default/templates/blob.html
8244+++ /dev/null
8245 @@ -1,47 +0,0 @@
8246- {% import "macros.html" as macros %}
8247- {% extends "base.html" %}
8248- {% block content %}
8249- <section>
8250- {{ macros::navigation(items=subnav_elements, title="Blob") }}
8251- <section class="info-bar">
8252- {%- if hint -%}
8253- <span class="hint" style="color: {{ color }}">{{ hint }}</span>
8254- {%- endif -%}
8255- <span>{{ file_name }}</span>
8256- <span class="right">{{ file_mode | filemode }} {{ file_size | human_bytes }}</span>
8257- </section>
8258- <div class="scrollable" style="max-width: {{ container_max_width }}">
8259- {% if is_binary %}
8260- <div class="blob-preview">
8261- {% if is_image %}
8262- <img height="1000px"
8263- width="1000px"
8264- alt="{{ file_name }}"
8265- src="{{ raw_url }}">
8266- </img>
8267- {% elif is_video %}
8268- <video controls>
8269- <source src="{{ raw_url }}" type="{{ file_type }}" />
8270- <p>
8271- <a href="{{ raw_url }}">raw url</a>
8272- </p>
8273- </video>
8274- {% elif is_audio %}
8275- <audio src="{{ raw_url }}" type="{{ file_type }}"></audio>
8276- {% else %}
8277- <center>
8278- <h3>Cannot render binary content</h3>
8279- </center>
8280- <a href="{{ raw_url }}">download</a>
8281- {% endif %}
8282- </div>
8283- {% else %}
8284- {% if is_markdown %}
8285- <div class="readme">{{ content | safe }}</div>
8286- {% else %}
8287- {{ content | safe }}
8288- {% endif %}
8289- {% endif %}
8290- </div>
8291- </section>
8292- {% endblock %}
8293 diff --git a/ayllu/themes/default/templates/build.html b/ayllu/themes/default/templates/build.html
8294deleted file mode 100644
8295index b373e02..0000000
8296--- a/ayllu/themes/default/templates/build.html
8297+++ /dev/null
8298 @@ -1,31 +0,0 @@
8299- {% extends "base.html" %}
8300- {% block content %}
8301- <section>
8302- <article>
8303- <header>
8304- <h1>Build: {{ build.id }}</h1>
8305- </header>
8306- <table>
8307- <thead>
8308- <th>date</th>
8309- <th>runtime</th>
8310- <th>success</th>
8311- </thead>
8312- <tr>
8313- <td>{{ build.timestamp }}</td>
8314- <td>{{ build.runtime }}</td>
8315- <td>{{ build.success }}</td>
8316- </tr>
8317- </table>
8318- </article>
8319- {% for step in steps %}
8320- <article>
8321- <header>
8322- {{ step.0 }}
8323- </header>
8324- <code> {{ step.1 }} </code>
8325- <code> {{ step.2 }} </code>
8326- </article>
8327- {% endfor %}
8328- </section>
8329- {% endblock %}
8330 diff --git a/ayllu/themes/default/templates/builds.html b/ayllu/themes/default/templates/builds.html
8331deleted file mode 100644
8332index 4f83aaa..0000000
8333--- a/ayllu/themes/default/templates/builds.html
8334+++ /dev/null
8335 @@ -1,28 +0,0 @@
8336- {% extends "base.html" %}
8337- {% block content %}
8338- <section>
8339- <article>
8340- <header>
8341- <h1>Builds</h1>
8342- </header>
8343- <table>
8344- <thead>
8345- <th>id</th>
8346- <th>date</th>
8347- <th>runtime</th>
8348- <th>success</th>
8349- </thead>
8350- {% for build in builds %}
8351- <tr>
8352- <td>
8353- <a href="/{{ collection }}/{{ name }}/builds/{{ build.id }}">{{ build.id }}</a>
8354- </td>
8355- <td>{{ build.timestamp }}</td>
8356- <td>{{ build.runtime }}</td>
8357- <td>{{ build.success }}</td>
8358- </tr>
8359- {% endfor %}
8360- </table>
8361- </article>
8362- </section>
8363- {% endblock %}
8364 diff --git a/ayllu/themes/default/templates/channel.html b/ayllu/themes/default/templates/channel.html
8365deleted file mode 100644
8366index e49bdd7..0000000
8367--- a/ayllu/themes/default/templates/channel.html
8368+++ /dev/null
8369 @@ -1,27 +0,0 @@
8370- {% import "macros.html" as macros %}
8371- {% extends "base.html" %}
8372- {% block content %}
8373- <section>
8374- <article>
8375- <header>
8376- {{ macros::navigation(items=discnav, title=channel) }}
8377- </header>
8378- <table>
8379- <thead>
8380- <th>nick</th>
8381- <th>timestamp</th>
8382- <th>message</th>
8383- </thead>
8384- <tbody>
8385- {% for message in messages %}
8386- <tr>
8387- <td>{{ message.nickname }}</td>
8388- <td>{{ message.timestamp }}</td>
8389- <td>{{ message.body | escape }}</td>
8390- </tr>
8391- {% endfor %}
8392- </tbody>
8393- </table>
8394- </article>
8395- </section>
8396- {% endblock %}
8397 diff --git a/ayllu/themes/default/templates/channels.html b/ayllu/themes/default/templates/channels.html
8398deleted file mode 100644
8399index 8926e29..0000000
8400--- a/ayllu/themes/default/templates/channels.html
8401+++ /dev/null
8402 @@ -1,29 +0,0 @@
8403- {% import "macros.html" as macros %}
8404- {% extends "base.html" %}
8405- {% block content %}
8406- <section>
8407- <article>
8408- <header>
8409- {{ macros::navigation(items=discnav, title="XMPP Channels") }}
8410- </header>
8411- <table>
8412- <thead>
8413- <th>name</th>
8414- <th>online</th>
8415- <th>messages</th>
8416- </thead>
8417- <tbody>
8418- {% for channel in channels %}
8419- <tr>
8420- <td>
8421- <a href="/discuss/xmpp/{{ channel.name }}">{{ channel.name }}</a>
8422- </td>
8423- <td>{{ channel.n_users }}</td>
8424- <td>{{ channel.n_messages }}</td>
8425- </tr>
8426- {% endfor %}
8427- </tbody>
8428- </table>
8429- </article>
8430- </section>
8431- {% endblock %}
8432 diff --git a/ayllu/themes/default/templates/chart.html b/ayllu/themes/default/templates/chart.html
8433deleted file mode 100644
8434index b8f2b26..0000000
8435--- a/ayllu/themes/default/templates/chart.html
8436+++ /dev/null
8437 @@ -1,10 +0,0 @@
8438- {% import "macros.html" as macros %}
8439- {% extends "base.html" %}
8440- {% block content %}
8441- <section>
8442- {{ macros::navigation(items=chartnav, title=chart_title) }}
8443- <section class="viewer">
8444- {{ chart | safe }}
8445- </section>
8446- </section>
8447- {% endblock %}
8448 diff --git a/ayllu/themes/default/templates/collection.html b/ayllu/themes/default/templates/collection.html
8449deleted file mode 100644
8450index b86aabf..0000000
8451--- a/ayllu/themes/default/templates/collection.html
8452+++ /dev/null
8453 @@ -1,35 +0,0 @@
8454- {% extends "base.html" %}
8455- {% block content %}
8456- <section class="column">
8457- <header>
8458- <h1>
8459- <a href="/{{ collection.name }}">{{ collection.name }}</a>
8460- {%- if is_hidden %} <span class="negative">[hidden]</span> {%- endif -%}
8461- </h1>
8462- {{ collection.description }}
8463- </header>
8464- <table>
8465- <thead>
8466- <tr>
8467- <th>Name</th>
8468- <th>Description</th>
8469- <th class="collapse">Last Updated</th>
8470- </tr>
8471- </thead>
8472- <tbody>
8473- {% for repo in repositories %}
8474- <tr>
8475- <td>
8476- <div class="name">
8477- <a href="/{{ collection.name }}/{{ repo.name }}">{{ repo.name }}</a>
8478- {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
8479- </div>
8480- </td>
8481- <td>{{ repo.description }}</td>
8482- <td>{{ repo.age }}</td>
8483- </tr>
8484- {% endfor %}
8485- </tbody>
8486- </table>
8487- </section>
8488- {% endblock %}
8489 diff --git a/ayllu/themes/default/templates/commit.html b/ayllu/themes/default/templates/commit.html
8490deleted file mode 100644
8491index 33dfb78..0000000
8492--- a/ayllu/themes/default/templates/commit.html
8493+++ /dev/null
8494 @@ -1,66 +0,0 @@
8495- {% extends "base.html" %}
8496- {% import "macros.html" as macros %}
8497- {% block content %}
8498- <section>
8499- <h5>Commit</h5>
8500- <section>
8501- <span><b>Author:</b></span>
8502- <span class="right">
8503- <a href="/{{ collection }}/{{ name }}/log?username={{ commit.author_name | urlencode }}&email={{ commit.author_email | urlencode }}">{{ commit.author_name }}</a>
8504- [<a href="mailto://{{ commit.author_email }}">{{ commit.author_email }}</a>]
8505- </span>
8506- </section>
8507- {% if distinct_author %}
8508- <section>
8509- <span><b>Committer:</b></span>
8510- <span class="right">
8511- <a href="#">{{ commit.committer_name }}</a>
8512- [<a href="mailto://{{ commit.committer_email }}">{{ commit.committer_email }}</a>]
8513- {{ commit.committer_epoch | format_epoch }}
8514- </span>
8515- </section>
8516- {% endif %}
8517- <section>
8518- <span><b>Hash:</b></span>
8519- <span class="right {% if commit.is_verified -%} positive {%- else -%} negative {%- endif -%}">{{ commit.id }}</span>
8520- </section>
8521- <section>
8522- <span><b>Timestamp:</b></span>
8523- <span class="right">{{ commit.author_epoch | format_epoch }} ({{ commit.epoch | friendly_time }})</span>
8524- </section>
8525- <br />
8526- <span class="positive">+{{ stats.insertions }}</span>
8527- <span class="negative">-{{ stats.deletions }}</span>
8528- <span>+/-{{ stats.files_changed }}</span>
8529- <span>
8530- <a href="/{{ collection }}/{{ name }}/tree/{{ commit_hash }}"
8531- role="button">browse</a>
8532- </span>
8533- <section>
8534- <b>{{ commit.summary }}</b>
8535- {% if extended_commit_message %}
8536- <div class="message">
8537- <pre>{{ commit.message }}</pre>
8538- </div>
8539- {% endif %}
8540- {% if commit.has_note %}
8541- <h5>Note</h5>
8542- <section>
8543- <span><b>Author:</b></span>
8544- <span class="right">
8545- <a href="/{{ collection }}/{{ name }}/log?username={{ note.author_name | urlencode }}&email={{ note.author_email | urlencode }}">{{ note.author_name }}</a>
8546- [<a href="mailto://{{ note.author_email }}">{{ commit.author_email }}</a>]
8547- </span>
8548- </section>
8549- <section>
8550- <span><b>Timestamp:</b></span>
8551- <span class="right">{{ note.author_epoch | format_epoch }} ({{ note.author_epoch | friendly_time }})</span>
8552- </section>
8553- <div class="message">
8554- <pre>{{ note.message }}</pre>
8555- </div>
8556- {% endif %}
8557- </section>
8558- <div class="scrollable">{{ diff.1 | safe }}</div>
8559- </section>
8560- {% endblock %}
8561 diff --git a/ayllu/themes/default/templates/config.html b/ayllu/themes/default/templates/config.html
8562deleted file mode 100644
8563index 091630d..0000000
8564--- a/ayllu/themes/default/templates/config.html
8565+++ /dev/null
8566 @@ -1,44 +0,0 @@
8567- {% extends "base.html" %}
8568- {% block content %}
8569- <section class="config-panel">
8570- <header>
8571- <h1>Site Configuration</h1>
8572- </header>
8573- <form action="/config" method="post">
8574- <label for="theme">
8575- <h3>items-per-page</h3>
8576- </label>
8577- <select id="items_per_page" name="items_per_page" required>
8578- <option value="50"
8579- {%- if config.items_per_page == 50 -%}
8580- selected
8581- {%- endif -%}>50</option>
8582- <option value="100"
8583- {%- if config.items_per_page == 100 -%}
8584- selected
8585- {%- endif -%}>100</option>
8586- <option value="150"
8587- {%- if config.items_per_page == 150 -%}
8588- selected
8589- {%- endif -%}>150</option>
8590- <option value="200"
8591- {%- if config.items_per_page == 200 -%}
8592- selected
8593- {%- endif -%}>200</option>
8594- </select>
8595- <label for="theme">
8596- <h3>theme</h3>
8597- </label>
8598- <select id="theme" name="theme" required>
8599- {% for theme in themes %}
8600- <option value="{{ theme }}"
8601- {%- if theme == config.theme -%}
8602- selected
8603- {%- endif -%}>{{ theme }}</option>
8604- {% endfor %}
8605- </select>
8606- <!-- Button -->
8607- <button type="submit">Submit</button>
8608- </form>
8609- </section>
8610- {% endblock %}
8611 diff --git a/ayllu/themes/default/templates/index.html b/ayllu/themes/default/templates/index.html
8612deleted file mode 100644
8613index c316623..0000000
8614--- a/ayllu/themes/default/templates/index.html
8615+++ /dev/null
8616 @@ -1,46 +0,0 @@
8617- {% extends "base.html" %}
8618- {% block content %}
8619- <section id="index">
8620- <section>
8621- <header>
8622- <h2>Collections</h2>
8623- <div class="rss-links">
8624- {{ "feed.svg" | emoji | safe }}
8625- <p>
8626- [<a href="/rss/firehose.xml">*</a>,
8627- <a href="/rss/1d.xml">1d</a>,
8628- <a href="/rss/1w.xml">1w</a>,
8629- <a href="/rss/1m.xml">1m</a>]
8630- </p>
8631- </div>
8632- </header>
8633- </section>
8634- <section class="scrollable">
8635- <table class="data-table">
8636- {% for collection in collections %}
8637- <tbody id="{{ collection.name }}">
8638- <tr class="group-label">
8639- <th colspan="3">
8640- <a class="collection" href="/{{ collection.name }}">{{ collection.name }}</a>
8641- </br>
8642- {{ collection.description }}
8643- </th>
8644- </tr>
8645- {% for repo in collection.repositories %}
8646- <tr>
8647- <td>
8648- <div class="name">
8649- <a href="/{{ collection.name }}/{{ repo.name }}">{{ repo.name }}</a>
8650- {%- if repo.is_mirror %} <span class="tiny-highlight">[mirror]</span> {%- endif -%}
8651- </div>
8652- </td>
8653- <td>{{ repo.description | truncate(length=50, end="...") }}</td>
8654- <td>{{ repo.age }}</td>
8655- </tr>
8656- {% endfor %}
8657- </tbody>
8658- {% endfor %}
8659- </table>
8660- </section>
8661- </section>
8662- {% endblock %}
8663 diff --git a/ayllu/themes/default/templates/lists.html b/ayllu/themes/default/templates/lists.html
8664deleted file mode 100644
8665index 7075000..0000000
8666--- a/ayllu/themes/default/templates/lists.html
8667+++ /dev/null
8668 @@ -1,27 +0,0 @@
8669- {% import "macros.html" as macros %}
8670- {% extends "base.html" %}
8671- {% block content %}
8672- <h1>Mailing Lists</h1>
8673- <section>
8674- <table class="data-table">
8675- <thead>
8676- <tr>
8677- <th>Name</th>
8678- <th>Description</th>
8679- <th>Address</th>
8680- </tr>
8681- </thead>
8682- <tbody>
8683- {% for list in lists %}
8684- <tr>
8685- <td>
8686- <a href="/mail/{{ list.name }}">{{ list.name }} [{{ list.address }}]</a>
8687- </td>
8688- <td>{{ list.description }}</td>
8689- <td>{{ list.address }}</td>
8690- </tr>
8691- {% endfor %}
8692- </tbody>
8693- </table>
8694- </section>
8695- {% endblock %}
8696 diff --git a/ayllu/themes/default/templates/log.html b/ayllu/themes/default/templates/log.html
8697deleted file mode 100644
8698index 0e1cd1a..0000000
8699--- a/ayllu/themes/default/templates/log.html
8700+++ /dev/null
8701 @@ -1,46 +0,0 @@
8702- {% import "macros.html" as macros %}
8703- {% extends "base.html" %}
8704- {% block content %}
8705- <section>
8706- <h1>Log</h1>
8707- </section>
8708- <section class="scrollable">
8709- <table class="data-table">
8710- <thead>
8711- <tr>
8712- <th>ID</th>
8713- <th>Flags</th>
8714- <th>Age</th>
8715- <th class="collapse">Author</th>
8716- <th>Message</th>
8717- </tr>
8718- </thead>
8719- <tbody>
8720- {% for commit in commits %}
8721- <tr>
8722- <td>
8723- <span class="{%- if commit.is_verified -%}positive{%- else -%}negative {%- endif -%}">
8724- <a href="/{{ collection }}/{{ name }}/commit/{{ commit.id }}">{{ commit.id | truncate(length=12, end="") }}</a>
8725- </span>
8726- </td>
8727- <td>
8728- {% if commit.has_note %}<div data-tooltip="Note">[N]</div>{% endif %}
8729- {% if commit.is_extended %}<div data-tooltip="Extended Commit">[E]</div>{% endif %}
8730- </td>
8731- <td>{{ commit.epoch | friendly_time }}</td>
8732- <td class="collapse">
8733- <a href="/{{ collection }}/{{ name }}/log?username={{ commit.author_name | urlencode }}&email={{ commit.author_email | urlencode }}">{{ commit.author_name }}</a>
8734- </td>
8735- <td>{{ commit.summary | truncate(length=85, end="...") }}</td>
8736- </tr>
8737- {% endfor %}
8738- </tbody>
8739- </table>
8740- {% if has_more %}
8741- <footer class="pagination">
8742- {%- set last_commit = commits | last -%}
8743- <span class="right"><a href="/{{ collection }}/{{ name }}/log/{{ last_commit.id }}"><b>next</b></a></span>
8744- </footer>
8745- {% endif %}
8746- </section>
8747- {% endblock %}
8748 diff --git a/ayllu/themes/default/templates/macros.html b/ayllu/themes/default/templates/macros.html
8749deleted file mode 100644
8750index 4d07b8c..0000000
8751--- a/ayllu/themes/default/templates/macros.html
8752+++ /dev/null
8753 @@ -1,16 +0,0 @@
8754- {% macro navigation(items, title="") %}
8755- <nav class="subnav">
8756- <ul>
8757- <h4>{{ title }}</h4>
8758- </ul>
8759- <ul>
8760- {% for item in items %}
8761- <li {% if item.2 %}class="active"{% endif %}>
8762- <a href="{{ item.1 }}">{{ item.0 }}</a>
8763- </li>
8764- {% endfor %}
8765- </ul>
8766- </nav>
8767- {% endmacro navigation %}
8768- {% macro commit_badge(commit, extended=false) %}
8769- {% endmacro commit_badge %}
8770 diff --git a/ayllu/themes/default/templates/nav.html b/ayllu/themes/default/templates/nav.html
8771deleted file mode 100644
8772index 5a502e6..0000000
8773--- a/ayllu/themes/default/templates/nav.html
8774+++ /dev/null
8775 @@ -1,12 +0,0 @@
8776- <nav>
8777- <a href="{%- if subpath_mode -%}/browse{%- else -%}/{%- endif -%}">
8778- <div class="logo">{{ "logo.svg" | emoji | safe }}</div>
8779- </a>
8780- <ul>
8781- {% for item in nav_elements %}
8782- <li {% if item.2 %}class="active"{% endif %}>
8783- <a href="{{ item.1 }}">{{ item.0 }}</a>
8784- </li>
8785- {% endfor %}
8786- </ul>
8787- </nav>
8788 diff --git a/ayllu/themes/default/templates/post.html b/ayllu/themes/default/templates/post.html
8789deleted file mode 100644
8790index ef9dc33..0000000
8791--- a/ayllu/themes/default/templates/post.html
8792+++ /dev/null
8793 @@ -1,25 +0,0 @@
8794- {% extends "base.html" %}
8795- {% block content %}
8796- <section class="stretch">
8797- <article>
8798- <header>
8799- <h1>{{ message.message_id }}</h1>
8800- </br>
8801- <h4>Export Message</h4>
8802- <p>
8803- <a href="/mail/export/{{ list_id }}/{{ thread_id }}/{{ message.message_id }}">mbox</a>
8804- <p>
8805- <b>From: {{ message.from }}</b>
8806- </br>
8807- <b>Subject: {{ message.subject }}</b>
8808- </br>
8809- <span class="right">{{ message.timestamp | format_epoch }}</span>
8810- </header>
8811- {% if message.is_patch %}
8812- {{ diff.1 | safe }}
8813- {% else %}
8814- <pre>{{ message.text | safe }}</pre>
8815- {% endif %}
8816- </article>
8817- </section>
8818- {% endblock %}
8819 diff --git a/ayllu/themes/default/templates/refs.html b/ayllu/themes/default/templates/refs.html
8820deleted file mode 100644
8821index 9a6b19c..0000000
8822--- a/ayllu/themes/default/templates/refs.html
8823+++ /dev/null
8824 @@ -1,89 +0,0 @@
8825- {% import "macros.html" as macros %}
8826- {% extends "base.html" %}
8827- {% block content %}
8828- <h1>Refs</h1>
8829- <section class="scrollable">
8830- {% if tags %}
8831- <h4 class="minor-header">Tags</h4>
8832- <table class="data-table">
8833- <thead>
8834- <tr>
8835- <th>Name</th>
8836- <th class="collapse">Age</th>
8837- <th class="collapse">Author</th>
8838- <th>Archive</th>
8839- </tr>
8840- </thead>
8841- <tbody>
8842- {% for tag in tags %}
8843- <tr>
8844- <td>
8845- <a href="/{{ collection }}/{{ name }}/refs/tag/{{ tag.name }}">{{ tag.name }}</a>
8846- </td>
8847- <td class="collapse">{{ tag.commit.epoch | friendly_time }}</td>
8848- <td class="collapse">{{ tag.author_name }}</td>
8849- <td>
8850- <a href="/{{ collection }}/{{ name }}/refs/archive/{{ tag.name }}.tar.gz"
8851- role="button">{{ tag.name }}.tar.gz</a>
8852- </td>
8853- </tr>
8854- {% endfor %}
8855- </tbody>
8856- </table>
8857- {% endif %}
8858- <h4 class="minor-header">Branches</h4>
8859- <table class="data-table">
8860- <thead>
8861- <tr>
8862- <th>Name</th>
8863- <th class="collapse">Age</th>
8864- <th>Commit</th>
8865- <th>Message</th>
8866- </tr>
8867- </thead>
8868- <tbody>
8869- {% for branch in branches %}
8870- <tr>
8871- <td>
8872- <a href="/{{ collection }}/{{ name }}/tree/{{ branch.name | urlencode }}">{{ branch.name }}</a>
8873- </td>
8874- <td class="collapse">{{ branch.head.epoch | friendly_time }}</td>
8875- <td>
8876- <a href="/{{ collection }}/{{ name }}/commit/{{ branch.head.id }}">
8877- {{ branch.head.id | truncate(length=8, end="") }}</a>
8878- </td>
8879- <td>{{ branch.head.summary | truncate(length=85, end="...") }}</td>
8880- </tr>
8881- {% endfor %}
8882- </tbody>
8883- </table>
8884- {% if notes %}
8885- <h4 class="minor-header">Notes</h4>
8886- <table class="data-table">
8887- <thead>
8888- <tr>
8889- <th>Commit</th>
8890- <th>Author</th>
8891- <th class="collapse">Age</th>
8892- <th>Message</th>
8893- </tr>
8894- </thead>
8895- <tbody>
8896- {% for note in notes %}
8897- <tr>
8898- <td>
8899- <a href="/{{ collection }}/{{ name }}/commit/{{ note.commit.id }}">
8900- {{ note.commit.id | truncate(length=8, end="") }}</a>
8901- </td>
8902- <td>{{ note.author_name }}</td>
8903- <td class="collapse">{{ note.author_epoch | friendly_time }}</td>
8904- <td>
8905- <pre>{{ note.message | truncate(length=32, end="...") }}</pre>
8906- </td>
8907- </tr>
8908- {% endfor %}
8909- </tbody>
8910- </table>
8911- {% endif %}
8912- </section>
8913- {% endblock %}
8914 diff --git a/ayllu/themes/default/templates/repo.html b/ayllu/themes/default/templates/repo.html
8915deleted file mode 100644
8916index 4dbc161..0000000
8917--- a/ayllu/themes/default/templates/repo.html
8918+++ /dev/null
8919 @@ -1,171 +0,0 @@
8920- {% extends "base.html" %}
8921- {% block content %}
8922- <section class="repo-view">
8923- <section class="pane-left">
8924- <section>
8925- <span>{{ latest_commit.author_name }} {{ latest_commit.epoch | friendly_time }}</span>
8926- </section>
8927- <!--<section>
8928- <span class="bold">FIXME commits</span>
8929- </section>-->
8930- {% if latest_commit.is_verified %}
8931- <span class="positive">
8932- {% else %}
8933- <span class="negative">
8934- {% endif %}
8935- {{ latest_commit.id | truncate(length=8, end="") }}
8936- </span>
8937- </span>
8938- <span class="right">
8939- <span class="bold yellow-badge">{{ refname }}</span>
8940- </span>
8941- <br />
8942- <span>
8943- <a href="/{{ collection }}/{{ name }}/commit/{{ latest_commit.id }}">
8944- {{ latest_commit.summary | truncate(length=60) }}
8945- </a>
8946- </span>
8947- <table class="tree">
8948- <thead>
8949- <tr>
8950- <th scope="col">File</th>
8951- <th class="collapse" scope="col">Commit</th>
8952- <th class="expand-xl" scope="col">Size</th>
8953- <th class="expand-xl" scope="col">Mode</th>
8954- <th scope="col">Time</th>
8955- </tr>
8956- </thead>
8957- <tbody>
8958- {% for item in tree %}
8959- <tr>
8960- {% if item.0.submodule %}
8961- <td>{{ item.0.name }} @ {{ item.0.submodule }}</td>
8962- {% else %}
8963- <td>
8964- <a href="{{ item.0.name | make_url(kind=item.0.kind) }}">
8965- {{ item.0.name }}
8966- {% if item.0.kind == "Submodule" %}<span class="tiny">ref</span>{% endif %}
8967- {% if item.0.kind == "Pointer" %}<span class="tiny">ptr</span>{% endif %}
8968- </a>
8969- </td>
8970- {% endif %}
8971- <td class="collapse">
8972- <a href="/{{ collection }}/{{ name }}/commit/{{ item.1.id }}">{{ item.1.summary | truncate(length=60) }}</a>
8973- </td>
8974- <td class="expand-xl">{{ item.0.size | human_bytes }}</td>
8975- <td class="expand-xl">{{ item.0.mode | filemode }}</td>
8976- <td>
8977- <a href="/{{ collection }}/{{ name }}/commit/{{ item.1.id }}">{{ item.1.epoch | friendly_time }}</a>
8978- </td>
8979- </tr>
8980- {% endfor %}
8981- </tbody>
8982- </table>
8983- <section class="readme scrollable">
8984- {{ readme | safe }}
8985- </section>
8986- </section>
8987- {% if show_details %}
8988- <section class="pane-right">
8989- <section class="repo-section">
8990- <h3>Clone</h3>
8991- <span class="bold">HTTP</span>
8992- <section class="clone">
8993- <input type="text" value="{{ http_clone_url }}" readonly>
8994- </section>
8995- {% if git_clone_url %}
8996- <section class="clone">
8997- <span class="bold">SSH</span>
8998- <input type="text" value="{{ git_clone_url }}" readonly>
8999- </section>
9000- {% endif %}
9001- </section>
9002- {% if chat_links or email_links %}
9003- <h3>Discussion</h3>
9004- {% if chat_links %}
9005- <h6>Chat</h6>
9006- {% for chat in chat_links %}
9007- <em>{{ chat.description }}, status:</em>
9008- <b class="{%- if chat.users_online -%}"
9009- "
9010- positive
9011- "
9012- {%- else -%}
9013- "
9014- negative
9015- "
9016- {%- endif -%}
9017- data-tooltip="{%- if chat.users_online -%} {{ chat.users_online }} Users Online {%- else -%} Offline {%- endif -%}">
9018- [{%- if chat.users_online -%}{{ chat.users_online }}{%- else -%}?{%- endif -%}]
9019- </b>
9020- <b>{{ chat.kind }}</b>
9021- <input type="text" value="{{ chat.url }}" readonly>
9022- {% endfor %}
9023- {% endif %}
9024- {% if email_links %}
9025- <h6>Mailing Lists</h6>
9026- {% for email in email_links %}
9027- <em>{{ email.description }}, 100+ threads</em>
9028- <b>mail</b>
9029- <input type="text" value="{{ email.url }}" readonly>
9030- {% endfor %}
9031- {% endif %}
9032- </section>
9033- {% endif %}
9034- <section>
9035- <h3>Subscribe</h3>
9036- <div class="rss-links">
9037- {{ "feed.svg" | emoji | safe }}
9038- <p>
9039- [<a href="{{ rss_link_all }}">*</a>,
9040- <a href="{{ rss_link_1d }}">1d</a>,
9041- <a href="{{ rss_link_1w }}">1w</a>,
9042- <a href="{{ rss_link_1m }}">1m</a>]
9043- </p>
9044- </div>
9045- </section>
9046- {% if sites_url %}
9047- <section class="repo-section">
9048- <div class="icon-header contrast">
9049- <h3>Homepage</h3>
9050- </div>
9051- <a href={{ sites_url }}>{{ sites_url }}</a>
9052- </section>
9053- {% endif %}
9054- <section class="repo-section">
9055- <div class="icon-header contrast">
9056- <h3>License</h3>
9057- </div>
9058- <i><b>{{ license }}</b></i>
9059- </section>
9060- {% if has_author_data %}
9061- <section>
9062- <h3>
9063- <a href="/{{ collection }}/{{ name }}/authors">Authors</a>
9064- </h3>
9065- {% if authors %}
9066- {% for author in authors %}
9067- <span>{{ author.0 }}:</span> <i> <span class="right">{{ author.2 }}% </i></span>
9068- </br>
9069- {% endfor %}
9070- {% endif %}
9071- {% endif %}
9072- {% if has_chart_data %}
9073- <h3>Analysis</h3>
9074- <section class="charts">
9075- {% if has_contribution_data %}
9076- <div class="chart">
9077- <a href="/{{ collection }}/{{ name }}/chart/activity/{{ latest_commit.id }}">{{ activity_chart | safe }}</a>
9078- </div>
9079- {% endif %}
9080- {% if has_language_data %}
9081- <div class="chart">
9082- <a href="/{{ collection }}/{{ name }}/chart/languages/{{ latest_commit.id }}">{{ language_chart | safe }}</a>
9083- </div>
9084- {% endif %}
9085- </section>
9086- {% endif %}
9087- </section>
9088- {% endif %}
9089- </section>
9090- {% endblock %}
9091 diff --git a/ayllu/themes/default/templates/rss_summary.html b/ayllu/themes/default/templates/rss_summary.html
9092deleted file mode 100644
9093index 0b98b21..0000000
9094--- a/ayllu/themes/default/templates/rss_summary.html
9095+++ /dev/null
9096 @@ -1,31 +0,0 @@
9097- <h1>Summary from {{ start_date }} to {{ end_date }}</h1>
9098- <h2>
9099- {{ n_tags }} tags and {{ n_commits }} commits added
9100- {% if entries | length > 1 %}
9101- across {{ n_projects }} projects{%- endif -%}
9102- </h2>
9103- {% for entry in entries %}
9104- <h2>Updates For {{ entry.name }}</h2>
9105- {% if entry.tags | length > 0 %}
9106- </br>
9107- <h3>
9108- <u>New Tags</u>
9109- </h3>
9110- {% endif %}
9111- {% if entry.commits | length > 0 %}
9112- </br>
9113- <h3>
9114- <u>New Commits</u>
9115- </h3>
9116- {% for commit in entry.commits %}
9117- <article>
9118- <header>
9119- <h4>
9120- <a href="{{ origin }}/{{ entry.name }}/commit/{{ commit.id }}">{{ commit.summary }} - {{ commit.author_name }}</a>
9121- </h4>
9122- </header>
9123- <pre>{{commit.message}}</pre>
9124- </article>
9125- {% endfor %}
9126- {% endif %}
9127- {% endfor %}
9128 diff --git a/ayllu/themes/default/templates/tag.html b/ayllu/themes/default/templates/tag.html
9129deleted file mode 100644
9130index 33aaa5f..0000000
9131--- a/ayllu/themes/default/templates/tag.html
9132+++ /dev/null
9133 @@ -1,33 +0,0 @@
9134- {% import "macros.html" as macros %}
9135- {% extends "base.html" %}
9136- {% block content %}
9137- <section class="stretch">
9138- <h1>{{ tag.name }}</h1>
9139- <section>
9140- <span><b>Author:</b></span>
9141- <span class="right">
9142- <a href="/{{ collection }}/{{ name }}/log?username={{ tag.commit.author_name | urlencode }}&email={{ tag.commit.author_email | urlencode }}">{{ tag.commit.author_name }}</a>
9143- [<a href="mailto://{{ tag.commit.author_email }}">{{ tag.commit.author_email }}</a>]
9144- </span>
9145- </section>
9146- <section>
9147- <span><b>Hash:</b></span>
9148- <span class="right {% if tag.commit.is_verified -%} positive {%- else -%} negative {%- endif -%}">{{ tag.commit.id }}</span>
9149- </section>
9150- <section>
9151- <span><b>Timestamp:</b></span>
9152- <span class="right">{{ tag.commit.author_epoch | format_epoch }} ({{ tag.commit.epoch | friendly_time }})</span>
9153- </section>
9154- <section>
9155- <span><b>Archive:</b></span>
9156- <span class="right">
9157- <a href="/{{ collection }}/{{ name }}/refs/archive/{{ tag.name }}.tar.gz"
9158- role="button">{{ tag.name }}.tar.gz</a>
9159- </span>
9160- </section>
9161- <h4>Summary</h4>
9162- <pre>
9163- {{tag.summary}}
9164- </pre>
9165- </section>
9166- {% endblock %}
9167 diff --git a/ayllu/themes/default/templates/thread.html b/ayllu/themes/default/templates/thread.html
9168deleted file mode 100644
9169index bdf31b1..0000000
9170--- a/ayllu/themes/default/templates/thread.html
9171+++ /dev/null
9172 @@ -1,21 +0,0 @@
9173- {% extends "base.html" %}
9174- {% block content %}
9175- <section class="stretch">
9176- <section class="message-header">
9177- <h6> {{ first_message.subject }} </h6>
9178- <h6><a href="/mail/{{list_name}}">{{list_name}}</a></h6>
9179- <h4>Export Thread</h4>
9180- <a href="/mail/export/{{ list.name }}/{{ thread_id }}">mbox</a>
9181- </section>
9182- <section class="messages">
9183- {% for message in thread %}
9184- <section class="message">
9185- <header class="message">
9186- From: {{message.mail_from}} | TODO: THE DATE
9187- </header>
9188- <pre> {{ message.content_body }} </pre>
9189- </section>
9190- {% endfor %}
9191- </section>
9192- </section>
9193- {% endblock %}
9194 diff --git a/ayllu/themes/default/templates/threads.html b/ayllu/themes/default/templates/threads.html
9195deleted file mode 100644
9196index 5ca4f06..0000000
9197--- a/ayllu/themes/default/templates/threads.html
9198+++ /dev/null
9199 @@ -1,47 +0,0 @@
9200- {% import "macros.html" as macros %}
9201- {% extends "base.html" %}
9202- {% block content %}
9203- <section>
9204- <header>
9205- <h1>{{ list.name }}</h1>
9206- <span class="right labels">
9207- {% for topic in list.topics %}<span class="yellow-badge">{{ topic }}</span>{% endfor %}
9208- </span>
9209- </header>
9210- <div class="mailing-list-details">
9211- <h4>{{ list.description }}</h4>
9212- </br>
9213- <h4>Subscribe</h4>
9214- <p>
9215- Send an e-mail to <a href="mailto:{{ list.request_address }}?subject=subscribe">{{ list.request_address }}</a> with the following subject: <code>subscribe</code>
9216- </p>
9217- <h4>Unsubscribe</h4>
9218- <p>
9219- Send an e-mail to <a href="mailto:{{ list.request_address }}?subject=unsubscribe">{{ list.request_address }}</a> with the following subject: <code>unsubscribe</code>
9220- </p>
9221- <h4>Export List</h4>
9222- <p>
9223- <a href="/mail/export/{{ list.name }}">mbox</a>
9224- </p>
9225- </div>
9226- <h4>Threads</h4>
9227- <section class="scrollable">
9228- <table class="data-table">
9229- <thead>
9230- <th scope="col">Subject</th>
9231- <th scope="col">From</th>
9232- <th scope="col">Replies</th>
9233- </thead>
9234- {% for thread in threads %}
9235- <tr>
9236- <td>
9237- <a href="/mail/thread/{{list.name}}/{{thread.message_id}}">{{ thread.subject | truncate(length=90) }}</a>
9238- </td>
9239- <td> {{ thread.mail_from }} </td>
9240- <td> {{ thread.reply_count }} </td>
9241- </tr>
9242- {% endfor %}
9243- </table>
9244- </section>
9245- </section>
9246- {% endblock %}
9247 diff --git a/ayllu/themes/default/templates/user.html b/ayllu/themes/default/templates/user.html
9248deleted file mode 100644
9249index 311042b..0000000
9250--- a/ayllu/themes/default/templates/user.html
9251+++ /dev/null
9252 @@ -1,2 +0,0 @@
9253- {% extends "base.html" %}
9254- {% block content %}User Profile{% endblock %}
9255 diff --git a/ayllu/themes/default/theme.css b/ayllu/themes/default/theme.css
9256deleted file mode 100644
9257index ec7c17d..0000000
9258--- a/ayllu/themes/default/theme.css
9259+++ /dev/null
9260 @@ -1,535 +0,0 @@
9261- @import url("../../../node_modules/open-props/open-props.min.css");
9262- @import url("../../../node_modules/open-props/normalize.min.css");
9263- @import url("../../../node_modules/open-props/buttons.min.css");
9264-
9265- @import url("./readme.css");
9266- @import url("./highlight.css");
9267- @import url("../../colors/nord.css");
9268-
9269- :root {
9270- font-size: 80%;
9271- }
9272-
9273- main {
9274- margin: 0;
9275- min-height: 90vh;
9276- }
9277-
9278- .content {
9279- width: 100%;
9280- }
9281-
9282- .container {
9283- margin: 0 auto;
9284- padding-top: 25px;
9285- }
9286-
9287- .pane-left {
9288- flex: 3;
9289- padding: 20px;
9290- max-width: 1200px;
9291- }
9292-
9293- .pane-right {
9294- flex: 1;
9295- padding: 20px;
9296- }
9297-
9298- .repo-view {
9299- display: flex;
9300- flex-wrap: wrap;
9301- }
9302-
9303- .data-table {
9304- white-space: nowrap;
9305- min-width: 90%;
9306- margin-top: 10px;
9307- }
9308-
9309- @media (max-width: 900px) {
9310- .charts {
9311- display: flex;
9312- justify-content: center;
9313- }
9314- }
9315-
9316- /* Media query for smaller screens */
9317- @media (max-width: 768px) {
9318- .pane-right {
9319- flex: none;
9320- width: 100%;
9321- }
9322-
9323- .pane-left {
9324- flex: none;
9325- width: 100%;
9326- }
9327-
9328- .pane-right {
9329- order: 1;
9330- }
9331- }
9332-
9333- a:visited {
9334- color: var(--link);
9335- text-decoration: none;
9336- }
9337-
9338- table {
9339- min-width: 100%;
9340- }
9341-
9342- .info-bar {
9343- background-color: var(--surface-3);
9344- color: var(--text-2);
9345- box-shadow: var(--shadow-3)
9346- }
9347-
9348- nav {
9349- display: flex;
9350- justify-content: space-between;
9351- align-items: center;
9352- background-color: var(--surface-1);
9353- padding: 10px;
9354- color: var(--text-1);
9355- box-shadow: var(--shadow-3);
9356- }
9357-
9358- nav.subnav {
9359- background-color: var(--surface-2);
9360- color: var(--text-1);
9361- box-shadow: var(--shadow-5);
9362- }
9363-
9364- nav .logo {
9365- font-size: 20px;
9366- font-weight: bold;
9367- }
9368-
9369- nav ul {
9370- list-style-type: none;
9371- margin: 0;
9372- padding: 0;
9373- display: flex;
9374- }
9375-
9376- nav ul li {
9377- margin-left: 20px;
9378- }
9379-
9380- nav ul li a {
9381- color: var(--link);
9382- text-decoration: none;
9383- }
9384-
9385- .right {
9386- float: right;
9387- text-align: right;
9388- }
9389-
9390- span .right {
9391- float: right;
9392- text-align: right;
9393- }
9394-
9395- span.bold {
9396- font-weight: var(--font-weight-7);
9397- }
9398-
9399- span.right {
9400- float: right;
9401- }
9402-
9403- span.hint {
9404- padding: 5px;
9405- font-weight: bold;
9406- text-decoration: underline;
9407- }
9408-
9409- span.tiny-highlight {
9410- font-size: var(--font-size-0);
9411- font-weight: var(--font-weight-0);
9412- color: var(--text-2);
9413- text-shadow:
9414- 0 0 10px var(--green-5),
9415- 0 0 25px var(--green-7);
9416- }
9417-
9418- .rss-links {
9419- display: flex;
9420- align-items: center;
9421- }
9422-
9423- .rss-links #emoji {
9424- margin-right: 10px;
9425- }
9426-
9427- .rss-links p {
9428- float: right;
9429- }
9430-
9431- p.commit-message {
9432- font-size: var(--font-size-0);
9433- font-weight: var(--font-weight-0);
9434- }
9435-
9436- header {
9437- color: var(--text-1);
9438- margin-bottom: 1em;
9439- }
9440-
9441- section.blame {
9442- display: grid;
9443- grid-template-columns: 1fr 5fr;
9444- }
9445-
9446- section.blame {
9447- article {
9448- margin-top: 0px !important;
9449- }
9450- }
9451-
9452- article.blame-right {
9453- padding: 0;
9454- width: 100%;
9455- }
9456-
9457- article.blame-left {
9458- padding: 0;
9459- table tbody tr td {
9460- text-align: left;
9461- }
9462- }
9463-
9464- footer {
9465- padding-top: 2em;
9466- text-align: center;
9467- font-size: smaller;
9468- }
9469-
9470- footer.rss {
9471- text-align: unset;
9472- }
9473-
9474- .scrollable {
9475- overflow: scroll;
9476- }
9477-
9478- .blob-preview {
9479- text-align: center;
9480- border-style: solid;
9481- border-color: pink;
9482- margin-top: 2em;
9483- padding: 1em;
9484- }
9485-
9486- .line-number {
9487- user-select: none;
9488- /* text-align: right;*/
9489- width: 0.5em;
9490- }
9491-
9492- .line {
9493- white-space: pre;
9494- padding-left: 5px;
9495- }
9496-
9497- .positive {
9498- color: var(--text-2);
9499- text-decoration: underline dotted var(--green-5);
9500- }
9501-
9502- .negative {
9503- color: var(--text-2);
9504- text-decoration: underline dotted var(--red-5);
9505- }
9506-
9507- table.code {
9508- font-family: var(--font-mono);
9509- margin-top: 1em;
9510- }
9511-
9512- .tree {
9513- --tree-border-width: 1px;
9514- --tree-border-color: var(--surface-2);
9515- --tree-cell-pad: var(--size-1);
9516- --tree-font: var(--font-mono);
9517- --tree-text: var(--text-2);
9518- border-collapse: collapse;
9519- border: none;
9520- background: none;
9521- white-space: nowrap;
9522- color: var(--tree-text); /* non-links get de-emphasized */
9523- font-family: var(--tree-font);
9524- }
9525-
9526- .tree :is(td, th) {
9527- padding: var(--tree-cell-pad);
9528- border-block: var(--tree-border-width) solid var(--tree-border-color);
9529- border-radius: 0;
9530- white-space: nowrap;
9531- text-align: start;
9532- }
9533-
9534- /* add classes for these? here is commit… and/or utility class */
9535- /* this cuts off messages about where Git will in logs */
9536- .tree :is(td, th):nth-child(2) {
9537- overflow: hidden;
9538- max-inline-size: 70ch;
9539- white-space: nowrap;
9540- text-overflow: ellipsis;
9541- }
9542-
9543- .tree :is(td, th):last-of-type {
9544- text-align: end;
9545- }
9546-
9547- code.highlighted {
9548- width: 100%;
9549- overflow: scroll;
9550- }
9551-
9552- .code > tbody > tr > td {
9553- text-align: revert;
9554- padding: 0 0 0 0;
9555- padding-right: 0.5em;
9556- }
9557-
9558- .yellow-badge {
9559- padding-inline: var(--size-1);
9560- border-width: var(--border-size-1);
9561- border-color: var(--pink-6);
9562- color: var(--pink-2);
9563- }
9564-
9565- #emoji {
9566- max-width: 40px;
9567- }
9568-
9569- .card {
9570- margin: 0px;
9571- padding: 0px;
9572- }
9573-
9574- section.viewer {
9575- margin-top: 5em;
9576- text-align: center;
9577- }
9578-
9579- section.viewer svg {
9580- display: inline-block;
9581- margin: 0 auto;
9582- }
9583-
9584- .stretch {
9585- overflow-x: auto;
9586- white-space: nowrap;
9587- }
9588-
9589- li.active {
9590- color: var(--text-2);
9591- text-decoration: underline var(--nord10);
9592- }
9593-
9594- article.collection {
9595- padding-top: 0px;
9596- padding-bottom: 0px;
9597- }
9598-
9599- article.collection .description {
9600- font-weight: var(--font-weight-1);
9601- }
9602-
9603- article.repository > .description {
9604- font-weight: var(--font-weight-4);
9605- margin-top: 5px;
9606- }
9607-
9608- .repositories > .segmented-4 {
9609- margin-top: 5px;
9610- }
9611-
9612- .segmented-5 {
9613- display: flex;
9614- justify-content: space-between;
9615- flex-wrap: wrap;
9616- }
9617-
9618- .segmented-5 > * {
9619- flex-basis: 20%;
9620- }
9621-
9622- @media (width < 768px) {
9623- .segmented-5 > * {
9624- flex-basis: 70%;
9625- }
9626- }
9627-
9628- @media (width < 480px) {
9629- .segmented-5 > * {
9630- flex-basis: 100%;
9631- }
9632- }
9633-
9634- .segmented-4 {
9635- display: flex;
9636- justify-content: space-between;
9637- flex-wrap: wrap;
9638- }
9639-
9640- .segmented-4 > * {
9641- flex-basis: 25%;
9642- }
9643-
9644- @media (width < 768px) {
9645- .segmented-4 > * {
9646- flex-basis: 50%;
9647- }
9648- }
9649-
9650- @media (width < 480px) {
9651- .segmented-4 > * {
9652- flex-basis: 100%;
9653- }
9654- }
9655-
9656- .segmented-3 {
9657- display: flex;
9658- justify-content: space-between;
9659- flex-wrap: wrap;
9660- }
9661-
9662- .segmented-3 > * {
9663- flex-basis: 33%;
9664- }
9665-
9666- @media (width < 768px) {
9667- .segmented-3 > * {
9668- flex-basis: 100%;
9669- }
9670- }
9671-
9672- @media (width < 768px) {
9673- .collapse {
9674- display: none;
9675- }
9676- }
9677-
9678- .segmented-2 {
9679- display: flex;
9680- justify-content: space-between;
9681- flex-wrap: wrap;
9682- }
9683-
9684- .segmented-2 > * {
9685- flex-basis: 50%;
9686- }
9687-
9688- @media (width < 768px) {
9689- .segmented-2 > * {
9690- flex-basis: 100%;
9691- }
9692- }
9693-
9694- .expand-xl {
9695- display: none;
9696- }
9697-
9698- @media (width > 1500px) {
9699- .expand-xl {
9700- display: revert;
9701- }
9702- }
9703-
9704- section.log {
9705- }
9706-
9707- article.log-entry {
9708- box-shadow: var(--shadow-3);
9709- overflow: auto;
9710- scrollbar-width: 0;
9711- margin: 1em;
9712- }
9713-
9714-
9715- .message {
9716- margin-top: 1em;
9717- font-size: var(--font-size-0);
9718- font-weight: var(--font-weight-0);
9719- }
9720-
9721- section.config-panel > form > button {
9722- margin-top: 1em;
9723- display: block;
9724- }
9725-
9726- img.rss-icon-feed {
9727- height: 4em;
9728- }
9729-
9730- .minor-header {
9731- text-decoration: underline;
9732- }
9733-
9734- .pseudo-row {
9735- }
9736-
9737- section.branch.card {
9738- padding-bottom: 1em;
9739- }
9740-
9741- :where(td, th) {
9742- padding: 5px;
9743- }
9744-
9745- :where(:is(td, th):not([align])) {
9746- text-align: left;
9747- }
9748-
9749- :where(table) {
9750- border-radius: unset;
9751- --nice-inner-radius: none;
9752- }
9753-
9754- .activity {
9755- text-align: center;
9756- }
9757-
9758- .clone > input {
9759- width: 100%;
9760- }
9761-
9762- thead.collection > tr th {
9763- padding-top: 2px;
9764- padding-bottom: 2px;
9765- }
9766-
9767- thead.collection > tr th > a {
9768- font-size: larger;
9769- }
9770-
9771- [data-tooltip]:hover::after {
9772- display: block;
9773- position: absolute;
9774- content: attr(data-tooltip);
9775- border: 1px solid black;
9776- padding: .25em;
9777- }
9778-
9779- @media (prefers-color-scheme: dark) {
9780- [data-tooltip]:hover::after {
9781- background-color: var(--nord11);
9782- }
9783- }
9784-
9785- a.collection {
9786- font-size: large;
9787- }
9788-
9789- section.message:nth-child(odd) {
9790- background: var(--nord0);
9791- }
9792-
9793- section.message:nth-child(even) {
9794- background: var(--nord1);
9795- }
9796 diff --git a/ayllu/themes/nord.css b/ayllu/themes/nord.css
9797new file mode 100644
9798index 0000000..b219b57
9799--- /dev/null
9800+++ b/ayllu/themes/nord.css
9801 @@ -0,0 +1,239 @@
9802+ /*
9803+ * Copyright (c) 2016-present Sven Greb <development@svengreb.de>
9804+ * This source code is licensed under the MIT license found in the license file.
9805+ */
9806+
9807+ :root {
9808+ --nord0: #2e3440;
9809+ --nord1: #3b4252;
9810+ --nord2: #434c5e;
9811+ --nord3: #4c566a;
9812+ --nord4: #d8dee9;
9813+ --nord5: #e5e9f0;
9814+ --nord6: #eceff4;
9815+ --nord7: #8fbcbb;
9816+ --nord8: #88c0d0;
9817+ --nord9: #81a1c1;
9818+ --nord10: #5e81ac;
9819+ --nord11: #bf616a;
9820+ --nord12: #d08770;
9821+ --nord13: #ebcb8b;
9822+ --nord14: #a3be8c;
9823+ --nord15: #b48ead;
9824+ }
9825+
9826+ a {
9827+ color: var(--nord10);
9828+ }
9829+
9830+ body {
9831+ color: var(--nord0);
9832+ background-color: var(--nord6);
9833+ }
9834+
9835+ nav {
9836+ a {
9837+ color: var(--nord6);
9838+ font-weight: bold;
9839+ font-size: large;
9840+ }
9841+ color: var(--nord6);
9842+ background-color: var(--nord9);
9843+ }
9844+
9845+ nav.subnav {
9846+ background-color: var(--nord2);
9847+ }
9848+
9849+ section.raised {
9850+ background-color: var(--nord4);
9851+ }
9852+
9853+ .data-table tbody tr:nth-child(odd) {
9854+ background-color: var(--nord5);
9855+ }
9856+
9857+ .data-table tbody tr:nth-child(even) {
9858+ background-color: var(--nord6);
9859+ }
9860+
9861+ section#tree > table > thead {
9862+ color: var(--nord4);
9863+ background-color: var(--nord10);
9864+ }
9865+
9866+ section#clone-links > .clone-link {
9867+ color: var(--nord6);
9868+ background-color: var(--nord10);
9869+ }
9870+
9871+ section#readme-header {
9872+ background-color: var(--nord7);
9873+ }
9874+
9875+ span#ref-badge {
9876+ background-color: var(--nord8);
9877+ color: var(--nord6);
9878+ border-color: var(--nord10);
9879+ }
9880+
9881+ span#ref-badge::before {
9882+ background-color: var(--nord7);
9883+ }
9884+
9885+ section.info-bar {
9886+ background-color: var(--nord2);
9887+ color: var(--nord6);
9888+ border-color: var(--nord2);
9889+ }
9890+
9891+ .positive {
9892+ color: var(--nord7);
9893+ border-bottom: 2px solid var(--nord7);
9894+ }
9895+
9896+ .negative {
9897+ color: var(--nord11);
9898+ border-bottom: 2px solid var(--nord11);
9899+ }
9900+
9901+ /* Syntax Highlighting */
9902+
9903+ section#code-viewer {
9904+ color: var(--nord0);
9905+ background-color: var(--nord5);
9906+ }
9907+
9908+ /*
9909+ span.ts_operator {}
9910+ span.ts_property {}
9911+ span.ts_punctuation {}
9912+ span.ts_punctuation_bracket {}
9913+ span.ts_punctuation_delimiter {}
9914+ span.ts_variable_builtin {}
9915+ span.ts_variable_parameter {}
9916+ */
9917+
9918+ span.ts_attribute {
9919+ color: var(--nord7);
9920+ }
9921+
9922+ span.ts_comment {
9923+ color: var(--nord10);
9924+ }
9925+
9926+ span.ts_constant {
9927+ color: var(--nord8);
9928+ }
9929+
9930+ span.ts_function_builtin {
9931+ color: var(--nord9);
9932+ }
9933+
9934+ span.ts_function {
9935+ color: var(--nord8);
9936+ }
9937+
9938+ span.ts_keyword {
9939+ color: var(--nord10);
9940+ }
9941+
9942+ span.ts_string {
9943+ color: var(--nord9);
9944+ }
9945+
9946+ span.ts_string_special {
9947+ color: var(--nord9);
9948+ }
9949+
9950+ span.ts_tag {
9951+ color: var(--nord9);
9952+ }
9953+
9954+ span.ts_type {
9955+ color: var(--nord8);
9956+ }
9957+
9958+ span.ts_type_builtin {
9959+ color: var(--nord3);
9960+ }
9961+
9962+ span.ts_variable {
9963+ color: var(--nord3);
9964+ }
9965+
9966+ span.ts_removal {
9967+ color: var(--primary-color);
9968+ }
9969+
9970+ span.ts_addition {
9971+ color: var(--primary-color);
9972+ }
9973+
9974+ /* Dark Mode */
9975+
9976+ @media (prefers-color-scheme: dark) {
9977+ a {
9978+ color: var(--nord5);
9979+ }
9980+
9981+ body {
9982+ color: var(--nord4);
9983+ background-color: var(--nord0);
9984+ }
9985+ nav {
9986+ a {
9987+ color: var(--nord6);
9988+ }
9989+ color: var(--nord6);
9990+ background-color: var(--nord2);
9991+ }
9992+
9993+ nav.subnav {
9994+ background-color: var(--nord3);
9995+ }
9996+
9997+ section#clone-links > .clone-link {
9998+ background-color: var(--nord10);
9999+ }
10000+
10001+ section.raised {
10002+ background-color: var(--nord1);
10003+ }
10004+
10005+ section#readme-header {
10006+ background-color: var(--nord3);
10007+ }
10008+
10009+ section#tree > table > thead {
10010+ color: var(--nord0);
10011+ background-color: var(--nord4);
10012+ }
10013+
10014+ section#tree tbody tr:nth-child(odd) {
10015+ background-color: var(--nord10);
10016+ }
10017+
10018+ section#tree tbody tr:nth-child(even) {
10019+ background-color: var(--nord9);
10020+ }
10021+
10022+ .data-table tbody tr:nth-child(odd) {
10023+ background-color: var(--nord10);
10024+ }
10025+
10026+ .data-table tbody tr:nth-child(even) {
10027+ background-color: var(--nord9);
10028+ }
10029+
10030+ section#code-viewer {
10031+ color: var(--nord5);
10032+ background-color: var(--nord0);
10033+ }
10034+
10035+ section.info-bar {
10036+ background-color: var(--nord4);
10037+ color: var(--nord2);
10038+ border-color: var(--nord5);
10039+ }
10040+ }
10041 diff --git a/biome.json b/biome.json
10042new file mode 100644
10043index 0000000..a6f7b65
10044--- /dev/null
10045+++ b/biome.json
10046 @@ -0,0 +1,15 @@
10047+ {
10048+ "vcs": {
10049+ "enabled": false,
10050+ "clientKind": "git",
10051+ "useIgnoreFile": false
10052+ },
10053+ "files": { "ignoreUnknown": false, "ignore": [] },
10054+ "formatter": { "enabled": true, "indentStyle": "tab" },
10055+ "organizeImports": { "enabled": true },
10056+ "linter": {
10057+ "enabled": true,
10058+ "rules": { "recommended": true }
10059+ },
10060+ "javascript": { "formatter": { "quoteStyle": "double" } }
10061+ }
10062 diff --git a/crates/git/src/lib.rs b/crates/git/src/lib.rs
10063index 01d47ac..762ec2d 100644
10064--- a/crates/git/src/lib.rs
10065+++ b/crates/git/src/lib.rs
10066 @@ -1,6 +1,6 @@
10067 pub use config::{ChatKind, ChatLink, Config};
10068 pub use error::{Error, ErrorKind};
10069- pub use lite::{Blob, Branch, Commit, Kind, Stats, Tag, TreeEntry};
10070+ pub use lite::{Blob, Branch, Commit, Kind, Note, Stats, Tag, TreeEntry};
10071 pub use scanner::{collection, collection_and_name, contains, git_dir, name, Scanner};
10072 pub use wrapper::{Selector, Wrapper};
10073
10074 diff --git a/crates/git/src/lite.rs b/crates/git/src/lite.rs
10075index 97f1c15..6599008 100644
10076--- a/crates/git/src/lite.rs
10077+++ b/crates/git/src/lite.rs
10078 @@ -1,3 +1,5 @@
10079+ use std::fmt::Display;
10080+
10081 use git2::{
10082 Blob as GitBlob, Branch as GitBranch, Commit as GitCommit, DiffStats as GitDiffStats,
10083 Note as GitNote, Tag as GitTag,
10084 @@ -210,7 +212,7 @@ impl From<(GitBranch<'_>, GitCommit<'_>)> for Branch {
10085 }
10086 }
10087
10088- #[derive(Serialize, Clone, Default)]
10089+ #[derive(Serialize, Clone, Default, PartialEq, Eq)]
10090 pub enum Kind {
10091 #[default]
10092 None,
10093 @@ -220,6 +222,30 @@ pub enum Kind {
10094 Tree,
10095 }
10096
10097+ impl PartialEq<&str> for Kind {
10098+ fn eq(&self, other: &&str) -> bool {
10099+ match self {
10100+ Kind::None => other.is_empty(),
10101+ Kind::Blob => *other == "blob",
10102+ Kind::Pointer => *other == "pointer",
10103+ Kind::Submodule => *other == "submodule",
10104+ Kind::Tree => *other == "tree",
10105+ }
10106+ }
10107+ }
10108+
10109+ impl Display for Kind {
10110+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10111+ match self {
10112+ Kind::None => Ok(()),
10113+ Kind::Blob => write!(f, "blob"),
10114+ Kind::Pointer => write!(f, "pointer"),
10115+ Kind::Submodule => write!(f, "submodule"),
10116+ Kind::Tree => write!(f, "tree"),
10117+ }
10118+ }
10119+ }
10120+
10121 #[derive(Serialize, Clone, Default)]
10122 pub struct TreeEntry {
10123 pub id: String,
10124 diff --git a/crates/timeutil/Cargo.toml b/crates/timeutil/Cargo.toml
10125index b75f72c..3163c00 100644
10126--- a/crates/timeutil/Cargo.toml
10127+++ b/crates/timeutil/Cargo.toml
10128 @@ -4,3 +4,4 @@ version = "0.1.0"
10129 edition = "2021"
10130
10131 [dependencies]
10132+ time = { version = "0.3.41", features = ["formatting"] }
10133 diff --git a/crates/timeutil/src/lib.rs b/crates/timeutil/src/lib.rs
10134index 4c2d67f..4208580 100644
10135--- a/crates/timeutil/src/lib.rs
10136+++ b/crates/timeutil/src/lib.rs
10137 @@ -51,3 +51,9 @@ pub fn friendly(seconds: u64) -> String {
10138 }
10139 message
10140 }
10141+
10142+ pub fn timestamp() -> String {
10143+ time::OffsetDateTime::now_utc()
10144+ .format(&time::format_description::well_known::Iso8601::DEFAULT)
10145+ .unwrap()
10146+ }
10147 diff --git a/package.json b/package.json
10148deleted file mode 100644
10149index 302bd32..0000000
10150--- a/package.json
10151+++ /dev/null
10152 @@ -1,14 +0,0 @@
10153- {
10154- "name": "ayllu",
10155- "version": "1.0.0",
10156- "main": "index.js",
10157- "repository": {
10158- "type": "git",
10159- "url": "https://ayllu-forge.org/ayllu/ayllu"
10160- },
10161- "author": "",
10162- "license": "AGPL-3.0",
10163- "dependencies": {
10164- "open-props": "^1.7.4"
10165- }
10166- }
10167 diff --git a/style.css b/style.css
10168new file mode 100644
10169index 0000000..e69de29
10170--- /dev/null
10171+++ b/style.css