diff options
| author | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2024-07-13 18:04:05 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2024-07-13 18:11:42 -0400 |
| commit | 81c4d517735983a5afd6e9dc800257c761598527 (patch) | |
| tree | 6b924707914d385126e6d330d2c628fd26f23a27 /static | |
| parent | 7f37518e4792f040a753a5d8d68d51e76cc0b2be (diff) | |
The `org' directory is no longer necessary
Diffstat (limited to 'static')
36 files changed, 2188 insertions, 0 deletions
diff --git a/static/css/fonts.css b/static/css/fonts.css new file mode 100644 index 0000000..05a70c6 --- /dev/null +++ b/static/css/fonts.css @@ -0,0 +1,59 @@ +/* Cantarell */ + +@font-face { + font-family: 'Cantarell'; + src: url('/static/font/Cantarell-Bold.otf') format('opentype'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Cantarell'; + src: url('/static/font/Cantarell-BoldOblique.otf') format('opentype'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Cantarell'; + src: url('/static/font/Cantarell-Oblique.otf') format('opentype'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Cantarell'; + src: url('/static/font/Cantarell-Regular.otf') format('opentype'); + font-weight: normal; + font-style: normal; +} + +/* Hack */ + +@font-face { + font-family: 'Hack'; + src: url('/static/font/Hack-Bold.ttf'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Hack'; + src: url('/static/font/Hack-BoldItalic.ttf'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Hack'; + src: url('/static/font/Hack-Italic.ttf'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Hack'; + src: url('/static/font/Hack-Regular.ttf'); + font-weight: normal; + font-style: normal; +} diff --git a/static/css/highlight.css b/static/css/highlight.css new file mode 100644 index 0000000..1e8899f --- /dev/null +++ b/static/css/highlight.css @@ -0,0 +1,61 @@ +.org-preprocessor, +.org-builtin { + color: #afd7d7; +} + +.org-comment-delimiter, +.org-comment { + color: #ff8700; +} + +.org-constant { + color: #87ffd7; +} + +.org-function-name { + color: #afd7ff; +} + +.org-keyword { + color: #1fb6d3; +} + +.org-negation-char { + color: #ff4b4b; +} + +.org-regexp-grouping-backslash { + color: #ffd787; +} + +.org-regexp-grouping-construct { + color: #d18aff; +} + +.org-doc, +.org-doc-string, +.org-string { + color: #ffaf87; +} + +.org-type { + color: #87ff87; +} + +.org-variable-name { + color: #ffd787; +} + +.org-warning { + color: #dd0000; +} + + +span.org-diff-added { text-decoration: none; background: #c0f8d0; font-family: "Inconsolata"; font-stretch: semi-condensed; font-weight: 600; font-style: normal; color: #000000; font-size: 10pt; } +span.org-diff-added a { background: #c0f8d0; font-family: "Inconsolata"; font-stretch: semi-condensed; font-weight: 600; font-style: normal; color: #000000; font-size: 10pt; text-decoration: underline; } +span.org-diff-indicator-added { text-decoration: none; background: #c0f8d0; font-weight: 700; font-family: "Inconsolata"; font-stretch: semi-condensed; font-style: normal; color: #000000; font-size: 10pt; } +span.org-diff-indicator-added a { background: #c0f8d0; font-weight: 700; font-family: "Inconsolata"; font-stretch: semi-condensed; font-style: normal; color: #000000; font-size: 10pt; text-decoration: underline; } +span.org-diff-removed { text-decoration: none; background: #ffd8d5; font-family: "Inconsolata"; font-stretch: semi-condensed; font-weight: 600; font-style: normal; color: #000000; font-size: 10pt; } +span.org-diff-removed a { background: #ffd8d5; font-family: "Inconsolata"; font-stretch: semi-condensed; font-weight: 600; font-style: normal; color: #000000; font-size: 10pt; text-decoration: underline; } +span.org-diff-indicator-removed { text-decoration: none; background: #ffd8d5; font-weight: 700; font-family: "Inconsolata"; font-stretch: semi-condensed; font-style: normal; color: #000000; font-size: 10pt; } +span.org-diff-indicator-removed a { background: #ffd8d5; font-weight: 700; font-family: "Inconsolata"; font-stretch: semi-condensed; font-style: normal; color: #000000; font-size: 10pt; text-decoration: underline; } diff --git a/static/css/normalize.css b/static/css/normalize.css new file mode 100644 index 0000000..192eb9c --- /dev/null +++ b/static/css/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..52d19af --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,557 @@ +[hidden] { display: none !important; } + +html { + font-family: "Cantarell", sans-serif; +} + +@media (max-width: 54em) { + html { + font-size: 14px; + line-height: 160%; + } +} + +@media (max-width: 26em) { + html { + font-size: 12px; + line-height: 150%; + } +} + +body { + max-width: 54rem; + margin: .5rem auto; + position: relative; +} + +@media (max-width: 54em) { + body { + max-width: 100%; + padding: 0 1rem; + } +} + +/* Top-level header. */ + +body > header { + display: flex; + align-items: center; +} + +@media screen and (max-width: 360px) { + body > header { + flex-direction: column; + flex-wrap: wrap; + justify-content: space-around; + } +} + +body > header > a > img { + max-width: 4rem; +} + +@media screen and (min-width: 480px) { + body > header > a { + padding-right: 1rem; + } +} + +body > header > nav ul { + list-style-type: none; + padding-left: 0; +} + +@media screen and (min-width: 480px) { + body > header > nav > ul > li { + display: inline-block; + } + + body > header > nav > ul li { + padding: 1rem 2rem; + border-bottom: 2px solid #d2d6dd; + text-align: center; + } + + body > header > nav > ul li > a { + color: #000; + text-decoration: none; + } + + body > header > nav > ul .drop-menu { + display: none; + position: absolute; + z-index: 1; + } + + body > header > nav > ul .drop-menu > li { + background: #fff; + } + + body > header > nav > ul > li:hover .drop-menu { + display: block; + } +} + +@media screen and (max-width: 480px) { + body > header > nav > ul .drop-menu { + list-style-type: circle; + padding-left: 1em; + } +} + +/* Horizontal lists. */ + +.horizontal-list { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + + list-style-type: none; + text-align: center; + + margin: 0; + padding: 8px 0; +} + +/* Pagination. */ + +#pagination { + display: flex; + justify-content: space-around; +} + +/* Tag clouds. */ + +#tag-cloud a { + color: #000; +} + +#tag-cloud li { + display: inline-block; + margin: 10px 5px; + padding: 5px 10px; + vertical-align: top; + + text-align: center; + + border-radius: 4px; + border-style: solid; + border-width: thin; +} + +/* Links. */ + +a:link, a:visited { color: #00E; text-decoration: none; } + +/* Figures */ + +figure > img { + display: block; + max-height: 600px; + max-width: 90%; + margin: auto; + + border-radius: 2px; +} + +figure > .org-svg { + display: block; + max-width: 90%; + margin: auto; +} + +figure > figcaption { + padding-top: 1rem; +} + +/* Embeds. */ + +.mastodon > iframe, +.peertube > iframe { + display: block; + width: 90%; + min-height: 100%; + margin: auto; + border-radius: 4px; +} + +/* Footnotes. */ + +.footdef { + margin-bottom: 1rem; +} + +.footdef > sup, .footpara { + display: inline; +} + +/* Comments & Webmention. */ + +textarea { + width: 100%; + height: 4rem; +} + +textarea, input[type=text], input[type=url] { + border: 1px solid #ccc; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); +} + +#webmention p .author-name { + margin: 4px; +} + +#webmention { + border-top: 2px solid #d2d6dd; +} + +ul.webmention-container { + list-style-type: none; +} + +ul.webmention-container > li { + border: 2px solid #d2d6dd; + border-bottom: none; +} + +ul.webmention-container > li:last-child { + border-bottom: 2px solid #d2d6dd; +} + +ul.webmention-container > li.comment { + padding: 0.5rem; +} + +ul.webmention-container > li.comment img.u-photo { + width: 4rem; + padding: 0 1rem 1rem 0; + float: left; + clear: both; + height: 100%; +} + +ul.webmention-container > li.interaction { + padding: 4px; +} + +ul.webmention-container > li.interaction img.u-photo { + max-width: 16px; + margin-right: 8px; + float: left; + clear: both; +} + +ul.webmention-container ul.comment-reactions { + margin-top: 8px; +} + +ul.webmention-container ul.comment-reactions li { + border-radius: 4px; + border: 1px solid #d2d6dd; + display: inline; + list-style-type: none; + padding: 4px; +} + +/* Webmention (or comment) source indicator. */ + +ul.webmention-container .comment { + position: relative; +} + +ul.webmention-container .comment .comment-source-identifier { + position: absolute; + top: 4px; + right: 4px; + max-width: 16px; +} + +/* Widget for comment reactions. */ + +.emoji-picker { + width: 51%; + height: 2.75em; + border: solid 1px; + overflow: scroll; + margin-top: 16px; +} + +.emoji-picker > span { + margin: 2px; + font-size: 2em; +} + + +/* Webmention form. */ + +form#webmention-form { + border-top: 2px solid #d2d6dd; + padding-top: 1rem; +} + +#webmention-input-group { + margin-top: 1rem; + display: grid; + grid-template-columns: 9fr 1fr; +} + +/* Comment form. */ + +.required:after { + content:" *"; + color: red; +} + +fieldset#comment-content { + display: grid; + grid-template-columns: 1fr 2fr; + grid-gap:5px; +} + +#comment-form #captcha-trigger-block label { + display: block; + margin-bottom: 16px; +} + +#comment-form img { + max-width: 100%; +} + +/* Source code. */ + +pre { + color: #f0e9dc; + font-family: "Hack", monospace; + font-size: 90%; + + background-color: #303539; + border-radius: 4px; + padding: 1rem; + overflow-x: auto; +} + +/* Footer. */ + +body > footer { + margin-top: 1.2rem; + border-top: 2px solid #d2d6dd; +} + +body > footer > div { + margin-top: 1rem; + display: flex; + align-items: center; +} + +body > footer > div > p { + margin: 0; + padding-right: 1rem; +} + +/* About page. */ + +.overline { + border-top: 2px solid #d2d6dd; + margin-top: 0.5rem; + overflow: auto; /* Not specific to overline. */ +} + +/* Tables. */ + +table { + text-align: center; + width: 100%; +} + +td { + padding: 0 1rem; +} + +tr:nth-child(even) { + background-color: #f2f2f2; +} + +/* Buttons. */ + +.lang button { + display: inline; + padding: .3em .6em .3em; + font-size: 75%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} + +.label-info { + background-color: #5bc0de; +} + +.label-good { + background-color: #5cb85c; + border-color: #4cae4c; +} + +.label-okay { + background-color: #ec971f; + border-color: #d58512; +} + +.label-bad { + background-color: #dc3545; +} + +/* Spoilered text. */ + +.spoiler { + background-color: #2e3436; + color: #2e3436; +} + +.spoiler:hover { + background-color: #f6f5f4; +} + +/* Folded sections. */ +.fold-hidden { + height: 300px; + overflow: hidden; + mask-image: linear-gradient(to bottom, black 0%, black 80%, transparent 100%); +} + +/* Styles specific to the "about" page. */ + +.portrait { + max-width: 30%; + float: right; +} + +.portrait > img { + max-width: 100%; +} + +.project-section td:nth-child(1) { + width: 15%; +} + +.project-section td:nth-child(2) { + width: 10%; +} + +.project-section td:nth-child(3) { + width: 80px; +} + +.project-section td:nth-child(4) { + width: 15%; +} + +.team-timeline-section img { + max-width: 32px; +} + +.team-timeline-section td:nth-child(1) { + width: 15%; +} + +.team-timeline-section td:nth-child(2) { + width: 40px; +} + +/* .team-timeline-section td:nth-child(3) { */ +/* width: 20%; */ +/* } */ + +.team-timeline-section td:nth-child(4) { + width: 15%; +} + +.team-timeline-section td:nth-child(5) { + width: 15%; +} + +.lang-c { + background-color: #555555; + color: #fff; +} + +.lang-cpp { + background-color: #f34b7d; + color: #fff; +} + +.lang-python { + background-color: #3572a5; + color: #ffdb4e; +} + +.lang-rust { + background-color: #dea584; + color: #fff; +} + +.lang-kotlin { + background-color: #f89917; + color: #fff; +} + +.lang-fennel { + background-color: #2ca02c; + color: #fff; +} + +.lang-elisp { + background-color: #c065db; + color: #fff; +} + +.lang-scheme { + background-color: #1e4aec; + color: #fff; +} + +.lang-ebuild { + background-color: #4d4270; + color: #fff; +} + +.article-front-matter { + border: 1px solid; + border-radius: 3px; + padding: 0.25em 1em; +} + +.article-warning { + background-image: url('/static/image/tango-emblem-important.png'); + background-repeat: no-repeat; + background-size: 4em 4em; + background-position: 1.5em 50%; + padding: 0.25em 1em 0.25em 7em; +} + +.article-future-interjection { + background-image: url('/static/image/clock.png'); + background-repeat: no-repeat; + background-size: 4em 4em; + background-position: 1.5em 50%; + padding: 0.25em 1em 0.25em 7em; +} + +#loading { + border: 1px solid; + width: fit-content; + padding: 5px; + margin: auto; +} + +fieldset { + padding: 12px; + margin: 15px; +} + +#rsvp-input input { + margin: 0px 16px; +} diff --git a/static/font/Cantarell-Bold.otf b/static/font/Cantarell-Bold.otf Binary files differnew file mode 100644 index 0000000..a5b620b --- /dev/null +++ b/static/font/Cantarell-Bold.otf diff --git a/static/font/Cantarell-BoldOblique.otf b/static/font/Cantarell-BoldOblique.otf Binary files differnew file mode 100644 index 0000000..5f49d64 --- /dev/null +++ b/static/font/Cantarell-BoldOblique.otf diff --git a/static/font/Cantarell-Oblique.otf b/static/font/Cantarell-Oblique.otf Binary files differnew file mode 100644 index 0000000..70539ed --- /dev/null +++ b/static/font/Cantarell-Oblique.otf diff --git a/static/font/Cantarell-Regular.otf b/static/font/Cantarell-Regular.otf Binary files differnew file mode 100644 index 0000000..fca6759 --- /dev/null +++ b/static/font/Cantarell-Regular.otf diff --git a/static/font/Hack-Bold.ttf b/static/font/Hack-Bold.ttf Binary files differnew file mode 100644 index 0000000..7ff4975 --- /dev/null +++ b/static/font/Hack-Bold.ttf diff --git a/static/font/Hack-BoldItalic.ttf b/static/font/Hack-BoldItalic.ttf Binary files differnew file mode 100644 index 0000000..3b137d9 --- /dev/null +++ b/static/font/Hack-BoldItalic.ttf diff --git a/static/font/Hack-Italic.ttf b/static/font/Hack-Italic.ttf Binary files differnew file mode 100644 index 0000000..d26198a --- /dev/null +++ b/static/font/Hack-Italic.ttf diff --git a/static/font/Hack-Regular.ttf b/static/font/Hack-Regular.ttf Binary files differnew file mode 100644 index 0000000..92a90cb --- /dev/null +++ b/static/font/Hack-Regular.ttf diff --git a/static/gpg.txt b/static/gpg.txt new file mode 100644 index 0000000..1de4e2a --- /dev/null +++ b/static/gpg.txt @@ -0,0 +1,87 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +tSuc5igEcOo6 +=dfc8 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/static/image/cbt-2023-04-13.jpg b/static/image/cbt-2023-04-13.jpg Binary files differnew file mode 100644 index 0000000..f691e45 --- /dev/null +++ b/static/image/cbt-2023-04-13.jpg diff --git a/static/image/cc-by-sa-4.0.png b/static/image/cc-by-sa-4.0.png Binary files differnew file mode 100644 index 0000000..003dd0a --- /dev/null +++ b/static/image/cc-by-sa-4.0.png diff --git a/static/image/clock.png b/static/image/clock.png Binary files differnew file mode 100644 index 0000000..d35c884 --- /dev/null +++ b/static/image/clock.png diff --git a/static/image/default-icon.png b/static/image/default-icon.png Binary files differnew file mode 100644 index 0000000..832f3ef --- /dev/null +++ b/static/image/default-icon.png diff --git a/static/image/favicon.ico b/static/image/favicon.ico Binary files differnew file mode 100644 index 0000000..842478f --- /dev/null +++ b/static/image/favicon.ico diff --git a/static/image/hourglass.gif b/static/image/hourglass.gif Binary files differnew file mode 100644 index 0000000..e1bf222 --- /dev/null +++ b/static/image/hourglass.gif diff --git a/static/image/i2p.svg b/static/image/i2p.svg new file mode 100644 index 0000000..dfc85a8 --- /dev/null +++ b/static/image/i2p.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path fill="#fff" d="M0 0v512h512V0H0z"/><path fill="#010101" d="M153 239c-19.3 10-36 24.8-54.3 36.5-9.8 8.3-25.3 12.6-28.7 26 10.3 23 37.5 34 53.6 53 23.2 14.8 12.4 37.4 10.8 58.5-.6 19.8 8 38.2 19.5 53.8 0 14-32 35-4 41 32-.2 63 1.3 95 0 19-6.4 41 2.6 61-2.3 23-1.2 47-.3 70-2.6 23-16-28-31-1.6-47 14-18 10.7-42 5.5-62 18-15 18.3-37 20-58 5-20 14-39 19-59-4-24-39-42-46-57 18-29 27-64 20-98-6.6-55-51-101-105-113-60-15-128 17-155.5 72.5-16 31-18 68.4-9.4 102 5.6 20.4 17 38 29.6 54.4z"/><path fill="#576e57" d="M328 25l1 1z"/><path fill="#444b19" d="M267 26l1 1-1-1z"/><path fill="#ffff01" d="M180 329l18 8c9.2-17.5 14.8-38.4 21.6-57 2.3-6.3 3.5-17 9.5-21.2 5-3.3 15-.5 20 0 11 1 24 .7 34-1.7 53-11 90-57 92-111 3-73-69-132-141-117-44 10-76 44-88 86-10 38 2 79 28 107 7 8 15 14 24 20 3.8 3 10 5 11 10 1.4 6-4.3 14-6 19-6 20-16.4 39-21.2 59z"/><path fill="#444b19" d="M275 27l1 1-1-1m-39 1l1 1-1-1m-8 2l1 1-1-1z"/><path fill="#6c6c02" d="M291 31l1 1-1-1z"/><path fill="#989801" d="M207 38l1 1-1-1zm105 2l1 1-1-1z"/><path fill="#444b19" d="M313 40l1 1-1-1z"/><path fill="#6c6c02" d="M320 44l1 1-1-1z"/><path fill="#444b19" d="M321 45l1 1-1-1m-130 2l1 1-1-1z"/><path fill="#989801" d="M192 47l1 1-1-1zm-2 1l1 1-1-1z"/><path fill="#444b19" d="M326 48l1 1-1-1z"/><path fill="#989801" d="M327 49l1 1-1-1"/><path fill="#9fc79f" d="M359 50l1 1-1-1z"/><path fill="#989801" d="M186 51l1 1-1-1m-1 1l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#6c6c02" d="M182 54l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#9fc79f" d="M334 55l1 1-1-1z"/><path fill="#6c6c02" d="M180 56l1 1-1-1z"/><path fill="#989801" d="M179 57l1 1-1-1m-1 1l1 1-1-1m-1 1l1 1z"/><path fill="#6c6c02" d="M176 60l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#989801" d="M341 61l1 1-1-1z"/><path fill="#6c6c02" d="M174 62l1 1-1-1z"/><path fill="#989801" d="M342 62l1 1-1-1z"/><path fill="#6c6c02" d="M173 63l1 1-1-1z"/><path fill="#989801" d="M343 63l1 1-1-1m-171 1l1 1-1-1m172 0l1 1-1-1m-173 1l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#010101" d="M299 75.5c-52.4 11-36.2 92.8 16 81.7 52.7-11.2 36.3-92.8-16-81.7z"/><path fill="#444b19" d="M354 77l1 1-1-1z"/><path fill="#6c6c02" d="M161 78l1 1-1-1z"/><path fill="#010101" d="M203 78.4c-42 5.6-52.3 62.7-12 80 7.5 3 18 2.6 26 1.4 48.6-7.5 34.8-87.8-14-81.4z"/><path fill="#989801" d="M289 78l1 1-1-1m35 0l1 1-1-1z"/><path fill="#444b19" d="M355 78l1 1-1-1z"/><path fill="#989801" d="M221 80l1 1-1-1z"/><path fill="#9fc79f" d="M328 80l1 1-1-1z"/><path fill="#6c6c02" d="M356 80l1 1-1-1zm-74 2l1 1-1-1z"/><path fill="#444b19" d="M185 83l1 1-1-1z"/><path fill="#989801" d="M281 83l1 1-1-1z"/><path fill="#6c6c02" d="M358 83l1 1-1-1m-174 1l1 1-1-1z"/><path fill="#444b19" d="M228 84l1 1-1-1z"/><path fill="#989801" d="M280 84l1 1-1-1z"/><path fill="#444b19" d="M359 84l1 1-1-1z"/><path fill="#6c6c02" d="M182 85l1 1-1-1m48 0l1 1-1-1z"/><path fill="#444b19" d="M334 85l1 1-1-1z"/><path fill="#6c6c02" d="M359 85l1 1-1-1m-203 1l1 1-1-1m25 0l1 1-1-1m50 0l1 1-1-1z"/><path fill="#989801" d="M277 86l1 1-1-1z"/><path fill="#444b19" d="M305 86l1 1-1-1zm30 0l1 1-1-1zm-103 1l1 1-1-1z"/><path fill="#989801" d="M276 87l1 1-1-1z"/><path fill="#fff" d="M283 134c3-6.3 4.8-9.4 10.2-11.7 15.7-7 24.5 8.4 22.8 21.7 38.8-11.2 14-72-22-54-4.3 2.3-8.2 6-11 10-3 4.5-5.2 9.5-5.7 15-.6 7 2.6 13 5.7 19z"/><path fill="#989801" d="M360 87l1 1-1-1m-205 1l1 1-1-1z"/><path fill="#6c6c02" d="M178 88l1 1-1-1z"/><path fill="#444b19" d="M276 88l1 1z"/><path fill="#576e57" d="M297 88l1 1-1-1z"/><path fill="#444b19" d="M235 89l1 1-1-1z"/><path fill="#6c6c02" d="M275 89l1 1-1-1z"/><path fill="#779375" d="M319 89l1 1-1-1z"/><path fill="#9fc79f" d="M201 90l1 1-1-1z"/><path fill="#FFF" d="M196 147c-2-8-3-15 3.4-21.8 10.5-11 28.7-3.6 30.6 10.8 9.5-9.4 6-29.2-3.2-37.7-12.5-11.8-32-10.4-43 2.7-3.6 4.5-5.2 9.2-6 15-.8 5.6.2 12 2.5 17 3.2 6.8 9 11.2 15.7 14z"/><path fill="#989801" d="M274 90l1 1-1-1z"/><path fill="#576e57" d="M320 90l1 1-1-1z"/><path fill="#989801" d="M175 91l1 1z"/><path fill="#444b19" d="M197 91l1 1-1-1z"/><path fill="#576e57" d="M215 91l1 1-1-1z"/><path fill="#989801" d="M273 91l1 1-1-1z"/><path fill="#9fc79f" d="M291 91l1 1-1-1zm-1 1l1 1-1-1z"/><path fill="#444b19" d="M324 92l1 1-1-1z"/><path fill="#6c6c02" d="M238 93l1 1-1-1m34 0l1 1-1-1z"/><path fill="#576e57" d="M288 93l1 1-1-1z"/><path fill="#95bb95" d="M289 93l1 1-1-1z"/><path fill="#444b19" d="M325 93l1 1-1-1z"/><path fill="#6c6c02" d="M173 94l1 1-1-1z"/><path fill="#9fc79f" d="M191 94l1 1-1-1z"/><path fill="#779375" d="M221 94l1 1-1-1z"/><path fill="#444b19" d="M222 94l1 1-1-1"/><path fill="#6c6c02" d="M271 94l1 1-1-1z"/><path fill="#95bb95" d="M287 94l1 1-1-1z"/><path fill="#779375" d="M326 94l1 1-1-1z"/><path fill="#444b19" d="M240 95l1 1-1-1z"/><path fill="#9fc79f" d="M286 95l1 1-1-1z"/><path fill="#989801" d="M364 95l1 1z"/><path fill="#9fc79f" d="M285 96l1 1-1-1zm-1 1l1 1-1-1z"/><path fill="#576e57" d="M126 99l1 1-1-1z"/><path fill="#9fc79f" d="M185 99l1 1-1-1z"/><path fill="#444b19" d="M366 99l1 1-1-1z"/><path fill="#6c6c02" d="M169 100l1 1-1-1z"/><path fill="#9fc79f" d="M184 100l1 1-1-1z"/><path fill="#989801" d="M345 100l1 1-1-1m21 0l1 1-1-1z"/><path fill="#6c6c02" d="M149 101l1 1-1-1z"/><path fill="#9fc79f" d="M182 102l1 1-1-1z"/><path fill="#444b19" d="M230 102l1 1-1-1z"/><path fill="#6c6c02" d="M244 102l1 1-1-1z"/><path fill="#444b19" d="M346 103l1 1-1-1zm-113 4l1 1-1-1z"/><path fill="#989801" d="M166 108l1 1-1-1z"/><path fill="#6c6c02" d="M246 108l1 1-1-1z"/><path fill="#576e57" d="M278 108l1 1-1-1"/><path fill="#444b19" d="M369 108l1 1-1-1m-223 1l1 1-1-1z"/><path fill="#576e57" d="M178 109l1 1-1-1z"/><path fill="#95bb95" d="M234 110l1 1-1-1z"/><path fill="#444b19" d="M372 117l1 1-1-1z"/><path fill="#576e57" d="M213 119l1 1-1-1z"/><path fill="#779375" d="M206 120l1 1-1-1"/><path fill="#9fc79f" d="M295 121l1 1-1-1z"/><path fill="#576e57" d="M304 121l1 1-1-1z"/><path fill="#444b19" d="M221 122l1 1-1-1z"/><path fill="#6c6c02" d="M143 123l1 1-1-1z"/><path fill="#9fc79f" d="M200 123l1 1-1-1z"/><path fill="#576e57" d="M223 123l1 1-1-1z"/><path fill="#9fc79f" d="M199 124l1 1-1-1z"/><path fill="#576e57" d="M224 124l1 1-1-1m54 0l1 1-1-1z"/><path fill="#9fc79f" d="M289 124l1 1-1-1z"/><path fill="#926a61" d="M310 124l1 1-1-1z"/><path fill="#95bb95" d="M311 124l1 1-1-1z"/><path fill="#9fc79f" d="M198 125l1 1-1-1z"/><path fill="#444b19" d="M225 125l1 1-1-1z"/><path fill="#989801" d="M247 125l1 1-1-1z"/><path fill="#6c6c02" d="M266 125l1 1-1-1z"/><path fill="#9fc79f" d="M312 125l1 1-1-1z"/><path fill="#444b19" d="M166 130l1 1-1-1z"/><path fill="#9fc79f" d="M195 130l1 1-1-1z"/><path fill="#95bb95" d="M315 130l1 1-1-1z"/><path fill="#926a61" d="M179 131l1 1-1-1z"/><path fill="#9fc79f" d="M229 131l1 1-1-1z"/><path fill="#779375" d="M233 131l1 1-1-1z"/><path fill="#576e57" d="M281 131l1 1-1-1z"/><path fill="#95bb95" d="M284 131l1 1-1-1z"/><path fill="#9fc79f" d="M282 132l1 1-1-1z"/><path fill="#989801" d="M345 132l1 1-1-1m-178 1l1 1-1-1z"/><path fill="#444b19" d="M180 133l1 1-1-1z"/><path fill="#576e57" d="M194 133l1 1-1-1z"/><path fill="#444b19" d="M232 133l1 1-1-1zm-1 2l1 1-1-1"/><path fill="#6c6c02" d="M169 138l1 1-1-1z"/><path fill="#444b19" d="M243 138l1 1-1-1m28 0l1 1-1-1z"/><path fill="#6c6c02" d="M342 138l1 1-1-1z"/><path fill="#9fc79f" d="M169 139l1 1-1-1z"/><path fill="#989801" d="M243 139l1 1-1-1z"/><path fill="#6c6c02" d="M272 139l1 1-1-1z"/><path fill="#444b19" d="M325 139l1 1-1-1z"/><path fill="#9fc79f" d="M342 139l1 1-1-1m-100 1l1 1-1-1z"/><path fill="#989801" d="M273 141l1 1-1-1z"/><path fill="#6c6c02" d="M274 142l1 1-1-1zm1 1l1 1-1-1z"/><path fill="#444b19" d="M190 144l1 1-1-1z"/><path fill="#6c6c02" d="M276 144l1 1-1-1z"/><path fill="#576e57" d="M192 145l1 1-1-1z"/><path fill="#444b19" d="M237 146l1 1-1-1z"/><path fill="#6c6c02" d="M175 147l1 1z"/><path fill="#989801" d="M237 147l1 1-1-1m42 0l1 1-1-1z"/><path fill="#6c6c02" d="M176 148l1 1-1-1z"/><path fill="#444b19" d="M236 148l1 1-1-1z"/><path fill="#989801" d="M280 148l1 1-1-1z"/><path fill="#444b19" d="M177 149l1 1-1-1z"/><path fill="#989801" d="M281 149l1 1-1-1z"/><path fill="#444b19" d="M178 150l1 1-1-1z"/><path fill="#6c6c02" d="M282 150l1 1-1-1z"/><path fill="#989801" d="M328 152l1 1-1-1z"/><path fill="#444b19" d="M182 153l1 1-1-1z"/><path fill="#6c6c02" d="M325 153l1 1-1-1z"/><path fill="#989801" d="M326 153l1 1-1-1"/><path fill="#444b19" d="M184 154l1 1-1-1z"/><path fill="#989801" d="M229 154l1 1-1-1"/><path fill="#6c6c02" d="M320 155l1 1-1-1z"/><path fill="#989801" d="M223 157l1 1-1-1m-32 1l1 1z"/><path fill="#9fc79f" d="M215 160l1 1-1-1z"/><path fill="#6c6c02" d="M143 162l1 1zm226 15l1 1-1-1z"/><path fill="#989801" d="M367 183l1 1z"/><path fill="#6c6c02" d="M149 184l1 1-1-1z"/><path fill="#989801" d="M366 185l1 1-1-1z"/><path fill="#444b19" d="M366 186l1 1-1-1z"/><path fill="#576e57" d="M390 187l1 1-1-1z"/><path fill="#989801" d="M364 190l1 1-1-1m-212 1l1 1-1-1z"/><path fill="#444b19" d="M364 191l1 1-1-1z"/><path fill="#989801" d="M153 193l1 1-1-1m207 5l1 1z"/><path fill="#6c6c02" d="M156 199l1 1-1-1z"/><path fill="#444b19" d="M157 200l1 1-1-1z"/><path fill="#6c6c02" d="M359 200l1 1-1-1z"/><path fill="#444b19" d="M359 201l1 1-1-1z"/><path fill="#989801" d="M161 207l1 1-1-1z"/><path fill="#444b19" d="M355 207l1 1-1-1z"/><path fill="#989801" d="M162.3 208.7c-108.2 202.2-54 101 0 0z"/><path fill="#444b19" d="M354 208l1 1-1-1z"/><path fill="#989801" d="M163 210l1 1-1-1m3 4l1 1-1-1m1 1l1 1-1-1m182 0l1 1-1-1m-181 1l1 1-1-1zm2 3l1 1-1-1m1 1l1 1-1-1m1 1l1 1-1-1m172 0l1 1-1-1z"/><path fill="#6c6c02" d="M173 222l1 1-1-1z"/><path fill="#989801" d="M343 222l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#6c6c02" d="M175 224l1 1-1-1z"/><path fill="#989801" d="M341 224l1 1-1-1z"/><path fill="#6c6c02" d="M176 225l1 1-1-1z"/><path fill="#989801" d="M177 226l1 1-1-1m1 1l1 1-1-1m1 1l1 1-1-1m158 0l1 1-1-1z"/><path fill="#6c6c02" d="M180 229l1 1-1-1z"/><path fill="#989801" d="M336 229l1 1-1-1z"/><path fill="#6c6c02" d="M181 230l1 1-1-1z"/><path fill="#989801" d="M334 230l1 1-1-1z"/><path fill="#444b19" d="M335 230l1 1-1-1z"/><path fill="#6c6c02" d="M182 231l1 1-1-1z"/><path fill="#989801" d="M184 232l1 1-1-1m1 1l1 1-1-1m1 1l1 1-1-1m1 1l1 1-1-1m3 2l1 1z"/><path fill="#6c6c02" d="M326 237l1 1-1-1z"/><path fill="#444b19" d="M191 238l1 1-1-1z"/><path fill="#989801" d="M192 238l1 1-1-1z"/><path fill="#6c6c02" d="M320 241l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#5a081a" d="M334 248c-222.7 176-111.3 88 0 0z"/><path fill="#ff013d" d="M237 270c-8.5 16-15.4 36.6-20 54-1.4 5-6.3 11.7-5 17 3.2 12.8 17.2 22.5 29 25.6 22.3 5.8 48.2 7.5 70-1 4-1.6 9.7-3 12-6.8 2-3.6.8-9 1.2-12.8 1-10.2 6.5-18.5 16.8-21 0-9 1.7-18.7 8.5-25.5 4-4 10.2-6 14-1.2 9.8 12.3-3.6 27.7 12.5 36.7 13.8-29.4 26.3-75-15-88.7-5.2-1.8-11.4-4.7-17-4-10.3 1-22.7 14-32.4 18.6-22.3 10-50.6 14-74.6 9z"/><path fill="#444b19" d="M199 243l1 1-1-1z"/><path fill="#989801" d="M315 244l1 1z"/><path fill="#444b19" d="M202 245l1 1-1-1z"/><path fill="#989801" d="M313 245l1 1-1-1z"/><path fill="#6c6c02" d="M314 245l1 1-1-1z"/><path fill="#989801" d="M311 246l1 1-1-1z"/><path fill="#444b19" d="M205 247l1 1-1-1z"/><path fill="#5a081a" d="M365 247l1 1-1-1z"/><path fill="#6c6c02" d="M206.7 249.3c-137.8 175-69 87.6 0 0z"/><path fill="#576e57" d="M401 250l1 1-1-1z"/><path fill="#5a081a" d="M181 251c-120.7 174-60.3 87 0 0zm147 0l1 1-1-1z"/><path fill="#a30127" d="M181 253c-120.7 172.7-60.3 86.3 0 0z"/><path fill="#ff013d" d="M137 338c3.2-4.2 6.6-7.2 11-10-2-4-6.7-6.5-7.5-11-1.6-9.4 12.2-21 20.5-21.8 4.4-.5 12.3 5 15.5 3 2.5-1.7 3.4-6.6 4.5-9.2 3-7.5 13-26 8-33.7-2.5-4-7.7-3.8-13.4-2.2-7.6 3-16 8-19.6 10-13.6 8-26.5 17-39 27-4 3-12.3 8-13.5 13-1.2 6 3 11 6.2 14 7.7 9 17.2 17 27.3 22z"/><path fill="#a30127" d="M185.7 252.3c-123.8 173-62 86.6 0 0zM170 255l1 1-1-1z"/><path fill="#989801" d="M289 255l1 1-1-1z"/><path fill="#a30127" d="M320 256l1 1-1-1z"/><path fill="#444b19" d="M230 257l1 1-1-1z"/><path fill="#989801" d="M281 257l1 1-1-1z"/><path fill="#5a081a" d="M318 257l1 1-1-1zm-154.7 1.7c-109 169-54.4 84.4 0 0z"/><path fill="#6c6c02" d="M205 258l1 1-1-1m23 0l1 1-1-1z"/><path fill="#5a081a" d="M161 259l1 1z"/><path fill="#6c6c02" d="M227 259l1 1-1-1z"/><path fill="#a30127" d="M160.3 260.7c-107 167.5-53.4 83.7 0 0z"/><path fill="#444b19" d="M226 260l1 1-1-1z"/><path fill="#a30127" d="M154 263l1 1-1-1"/><path fill="#989801" d="M223 267l1 1-1-1z"/><path fill="#5a081a" d="M386 267l1 1-1-1m-198 1l1 1-1-1z"/><path fill="#989801" d="M201 268l1 1-1-1z"/><path fill="#444b19" d="M223.3 268.7c-149 162.2-74.4 81 0 0z"/><path fill="#6c6c02" d="M222 270c-148 161.3-74 80.7 0 0z"/><path fill="#5a081a" d="M236 270l1 1-1-1z"/><path fill="#989801" d="M200 271c-133.3 160.7-66.7 80.3 0 0z"/><path fill="#5a081a" d="M245 271l1 1-1-1"/><path fill="#a30127" d="M139 273l1 1-1-1z"/><path fill="#5a081a" d="M186.3 273.7c-124.2 159-62 79.4 0 0z"/><path fill="#a30127" d="M137 274l1 1-1-1z"/><path fill="#6c6c02" d="M199 274l1 1-1-1z"/><path fill="#a30127" d="M185 275l1 1z"/><path fill="#444b19" d="M198.5 276.6c-132.3 157-66.2 78.5 0 0z"/><path fill="#a30127" d="M234 275l1 1-1-1zm-100 1l1 1-1-1zm-1 1l1 1-1-1z"/><path fill="#5a081a" d="M131 278l1 1-1-1m53.3.7s0 1 .4.6c0 0 0-1-.4-.6zm-54.3.3l1 1-1-1z"/><path fill="#a30127" d="M183.3 280.7c-122.2 154.2-61 77 0 0z"/><path fill="#5a081a" d="M127 281l1 1-1-1z"/><path fill="#a30127" d="M125.7 282.3c-.5.5.6.4.6.4.5-.5-.6-.4-.6-.4z"/><path fill="#6c6c02" d="M218 282l1 1-1-1z"/><path fill="#5a081a" d="M231 282l1 1-1-1m-107 1l1 1-1-1z"/><path fill="#9fc79f" d="M83 286l1 1-1-1z"/><path fill="#444b19" d="M216.3 287.7c-144.2 149.5-72 74.7 0 0z"/><path fill="#a30127" d="M180 289l1 1-1-1z"/><path fill="#989801" d="M215 289c-143.3 148.7-71.7 74.3 0 0z"/><path fill="#6c6c02" d="M193 290l1 1-1-1m22.3.7s0 1 .4.6c0 0 0-1-.4-.6z"/><path fill="#989801" d="M192.3 293.7c-128.2 145.5-64 72.7 0 0z"/><path fill="#a30127" d="M109 295l1 1-1-1m57 0c-110.7 144.7-55.3 72.3 0 0z"/><path fill="#5a081a" d="M178.3 295.7c-119 144.2-59.4 72 0 0z"/><path fill="#6c6c02" d="M191.3 295.7c-127.5 144.2-63.7 72 0 0z"/><path fill="#989801" d="M213 295l1 1-1-1z"/><path fill="#a30127" d="M108 296l1 1-1-1m46.7.3c-103 143.8-51.6 72 0 0z"/><path fill="#6c6c02" d="M213 296l1 1-1-1z"/><path fill="#a30127" d="M107 297l1 1z"/><path fill="#5a081a" d="M171.3 297.7c-114.2 143-57 71.4 0 0z"/><path fill="#989801" d="M191 297l1 1-1-1z"/><path fill="#a30127" d="M226 297l1 1-1-1m-119.7 1.7c-71 142.2-35.4 71 0 0z"/><path fill="#5a081a" d="M177 298l1 1-1-1z"/><path fill="#444b19" d="M190.3 298.7c-127 142.2-63.4 71 0 0z"/><path fill="#5a081a" d="M173.7 299.3c-115.8 141.8-58 71 0 0z"/><path fill="#a30127" d="M176 299l1 1-1-1m-74 3c-68 140-34 70 0 0z"/><path fill="#5a081a" d="M147 302c1 1 1 1 0 0"/><path fill="#6c6c02" d="M188 304l1 1-1-1z"/><path fill="#989801" d="M210 304l1 1-1-1z"/><path fill="#6c6c02" d="M209 306l1 1-1-1z"/><path fill="#444b19" d="M208 312c-138.7 133.3-69.3 66.7 0 0z"/><path fill="#a30127" d="M106 312l1 1z"/><path fill="#444b19" d="M185 312l1 1-1-1z"/><path fill="#6c6c02" d="M207.3 312.7c-138.2 133-69 66.4 0 0z"/><path fill="#a30127" d="M107 313l1 1-1-1z"/><path fill="#5a081a" d="M140 313c-93.3 132.7-46.7 66.3 0 0z"/><path fill="#989801" d="M185 313l1 1-1-1z"/><path fill="#a30127" d="M108 314l1 1-1-1z"/><path fill="#989801" d="M206.3 314.7c-137.5 131.5-68.7 65.7 0 0z"/><path fill="#a30127" d="M109 315l1 1-1-1m1.3 1.7c-73.5 130.2-36.7 65 0 0z"/><path fill="#444b19" d="M183.3 317.7c-122.2 129.5-61 64.7 0 0z"/><path fill="#a30127" d="M111 318l1 1-1-1m1 1l1 1-1-1z"/><path fill="#989801" d="M183 319l1 1-1-1z"/><path fill="#5a081a" d="M218 319l1 1-1-1z"/><path fill="#a30127" d="M382 320l1 1-1-1m-267 1l1 1-1-1m1 1l1 1zm1 1l1 1-1-1m1 1l1 1-1-1m28 1l1 1-1-1m2.3 1.7c-99 123.5-49.4 61.7 0 0z"/><path fill="#444b19" d="M180 326l1 1-1-1z"/><path fill="#a30127" d="M335 326l1 1-1-1m-213 1l1 1-1-1m93.3.7c-143.5 123-71.7 61.4 0 0zm-92.3.3l1 1-1-1z"/><path fill="#5a081a" d="M147 328l1 1-1-1z"/><path fill="#6c6c02" d="M201 328l1 1-1-1z"/><path fill="#444b19" d="M201.3 329.7c-134.2 121.5-67 60.7 0 0z"/><path fill="#5a081a" d="M214.3 329.7c-143 121.5-71.4 60.7 0 0z"/><path fill="#989801" d="M184 330l1 1-1-1z"/><path fill="#5a081a" d="M373 333l1 1z"/><path fill="#444b19" d="M191 334l1 1-1-1z"/><path fill="#989801" d="M192 334l1 1-1-1z"/><path fill="#6c6c02" d="M199 334l1 1-1-1z"/><path fill="#a30127" d="M327 334l1 1-1-1z"/><path fill="#5a081a" d="M374 334l1 1-1-1z"/><path fill="#6c6c02" d="M194.3 335.7c-129.5 117.5-64.7 58.7 0 0z"/><path fill="#5a081a" d="M375 335l1 1-1-1m-241.3 1.3c-89 117-44.6 58.6 0 0z"/><path fill="#444b19" d="M195 336l1 1-1-1z"/><path fill="#989801" d="M198.3 336.7c-132.2 117-66 58.4 0 0z"/><path fill="#9fc79f" d="M104 337l1 1-1-1z"/><path fill="#444b19" d="M197 337l1 1-1-1z"/><path fill="#926a61" d="M343 338l1 1-1-1z"/><path fill="#fbb5a7" d="M344 338.7c-8.5 3.5-8.5 27.2-1.4 32.3 3.8 2.8 17.2-.7 21.4-2.3 23.5-9.5-4.6-36-20-30z"/><path fill="#444b19" d="M341 339l1 1-1-1m-1 1l1 1-1-1z"/><path fill="#926a61" d="M357 340l1 1-1-1z"/><path fill="#a30127" d="M211 341l1 1-1-1zm1 1l1 1-1-1z"/><path fill="#926a61" d="M339 342l1 1-1-1m22 0l1 1-1-1z"/><path fill="#5a081a" d="M324 343l1 1-1-1z"/><path fill="#b9867c" d="M159 344c-106 112-53 56 0 0z"/><path fill="#a30127" d="M213 344l1 1-1-1z"/><path fill="#444b19" d="M364 344l1 1-1-1z"/><path fill="#fbb5a7" d="M201 365c-6.8-9-22-17.4-33-19.5-4.5-1-12.2-1.7-14.2 3.6-2.6 7 4 13 9.2 16 11.8 6 26 4 38 1z"/><path fill="#926a61" d="M168 345l1 1-1-1z"/><path fill="#5a081a" d="M214.3 345.7c-143 111-71.4 55.4 0 0z"/><path fill="#444b19" d="M365 345l1 1-1-1z"/><path fill="#a30127" d="M215 347l1 1-1-1z"/><path fill="#b9867c" d="M176 348l1 1-1-1z"/><path fill="#444b19" d="M178.7 349.3c-119 108.5-59.6 54.3 0 0z"/><path fill="#5a081a" d="M216 349l1 1-1-1z"/><path fill="#926a61" d="M180.7 350.3c-120.5 107.8-60.3 54 0 0z"/><path fill="#5a081a" d="M217 350l1 1-1-1z"/><path fill="#444b19" d="M370 350l1 1-1-1z"/><path fill="#b9867c" d="M182 351l1 1-1-1z"/><path fill="#576e57" d="M183 351l1 1-1-1z"/><path fill="#5a081a" d="M218 351l1 1-1-1z"/><path fill="#576e57" d="M371 351l1 1-1-1z"/><path fill="#444b19" d="M185 352l1 1-1-1zm-32 2c-102 105.3-51 52.7 0 0z"/><path fill="#5a081a" d="M222 356l1 1-1-1"/><path fill="#926a61" d="M192 357l1 1-1-1z"/><path fill="#5a081a" d="M223 357l1 1-1-1z"/><path fill="#b9867c" d="M155 358l1 1-1-1z"/><path fill="#444b19" d="M338 358l1 1-1-1z"/><path fill="#b9867c" d="M194.7 359.3c-129.8 101.8-65 51 0 0z"/><path fill="#5a081a" d="M320 361l1 1-1-1z"/><path fill="#926a61" d="M199 362l1 1-1-1z"/><path fill="#a30127" d="M319 362l1 1-1-1z"/><path fill="#926a61" d="M200 363l1 1-1-1z"/><path fill="#5a081a" d="M229 363l1 1-1-1z"/><path fill="#444b19" d="M161 364l1 1-1-1z"/><path fill="#926a61" d="M162 364l1 1-1-1m39 0l-1 2h2c0-2 .3-1.4-1-2z"/><path fill="#444b19" d="M163.7 365.3c-109 97.8-54.6 49 0 0z"/><path fill="#b9867c" d="M199 365l1 1-1-1z"/><path fill="#5a081a" d="M311.7 365.3c-207.8 97.8-104 49 0 0z"/><path fill="#444b19" d="M196 366c-130.7 97.3-65.3 48.7 0 0z"/><path fill="#576e57" d="M134 367l1 1-1-1z"/><path fill="#b9867c" d="M191 367l1 1-1-1z"/><path fill="#a30127" d="M244 367l1 1-1-1m61 0l1 1-1-1z"/><path fill="#444b19" d="M366 367l1 1-1-1z"/><path fill="#926a61" d="M364 368l1 1-1-1z"/><path fill="#5a081a" d="M298 369l1 1-1-1z"/><path fill="#95bb95" d="M357 385c-17 3.2-27 3-31-16-12 1.6-23.6 7.8-36 9.6-4 .6-11-1-14.3 2-5 4.8-5.4 14-5.7 20.4-.3 4.7-2.4 9.3-1.6 14 1.7 10.3 13.3 21.2 19.7 29 5 5.7 11 14.3 18 16.5 9 2.4 17-4.7 23-8.8 10-6 20-13.5 25-23.7 3-4.8 1-11.8 1-17 1-8.7 2-17.3 2-26z"/><path fill="#444b19" d="M358 370l1 1-1-1z"/><path fill="#576e57" d="M305 375l1 1-1-1z"/><path fill="#9fc79f" d="M157 384c0 14-.5 26 5.2 39 4.8 11 11 21 19 30 7 7.7 14.8 9 24.8 9 17.2 0 33.7-6.2 42.5-22 11-20 3.6-55-21.5-61.3-8-2-23.7 6.3-32 8-13.7 3-24.8-.4-38-2.7z"/><path fill="#779375" d="M290 378l1 1-1-1zm-61 1l1 1-1-1z"/><path fill="#576e57" d="M216 380c-144 88-72 44 0 0z"/><path fill="#444b19" d="M274.7 381.3c-183 87-91.6 43.6 0 0z"/><path fill="#779375" d="M232.3 381.7c-155 87-77.4 43.4 0 0z"/><path fill="#95bb95" d="M211 382c-140.7 86.7-70.3 43.3 0 0z"/><path fill="#444b19" d="M273 385c-182 84.7-91 42.3 0 0z"/><path fill="#779375" d="M208 383l1 1-1-1m27 0l1 1-1-1m1 1l1 1-1-1"/><path fill="#576e57" d="M162 385l1 1-1-1z"/><path fill="#444b19" d="M238 385l1 1-1-1zm1 1l1 1-1-1m33 0l1 1-1-1"/><path fill="#9fc79f" d="M137 387l1 1-1-1z"/><path fill="#779375" d="M191 387l1 1-1-1z"/><path fill="#576e57" d="M240 387l1 1-1-1m1 1l1 1-1-1m1 1l1 1-1-1m-106 1l1 1-1-1m107 0l1 1-1-1z"/><path fill="#9fc79f" d="M136 391l1 1-1-1z"/><path fill="#576e57" d="M244 391l1 1-1-1m2 3l1 1z"/><path fill="#779375" d="M155 395l1 1-1-1z"/><path fill="#444b19" d="M251 403l1 1-1-1z"/><path fill="#576e57" d="M365 406c-1 6.8 1.4 18.2-1.5 24.2-7.2 15.3-24.4 25.4-38.5 33.7-6.6 3-13.2 7-21 5-16.8-6-21-26-35-34 0 9 .3 16 3.7 25 17.6 42 71.5 11 89.5-14 7.4-11 13.3-30 2.8-40m-208 3l1 1-1-1z"/><path fill="#779375" d="M268 416l1 1-1-1zm-15 9l1 1z"/><path fill="#444b19" d="M274 426l1 1z"/><path fill="#779375" d="M275 427l1 1-1-1zm3 4l1 1-1-1z"/><path fill="#576e57" d="M155 432c2.6 11 11 20.8 17.3 30 2 3.3 4.5 8.6 8 10.4 4.6 2.4 11.7 2.2 16.7 2.8 15.3 1.8 33.3 2.6 48-2.5 5.6-2 11.8-4 14.7-9.7 3-5.7-.6-14-1.7-20-5.6 6-9.4 12.4-16 17.5-14.2 11-43 17.8-59 6-12.4-9-17.4-24.2-28-34.5m11 0l1 1-1-1m113 0l1 1-1-1z"/><path fill="#444b19" d="M362 432l1 1-1-1z"/><path fill="#576e57" d="M167 433l1 1-1-1zm182 0l1 1-1-1z"/><path fill="#444b19" d="M280 434l1 1-1-1m1 1l1 1-1-1z"/><path fill="#576e57" d="M282 436l1 1-1-1z"/><path fill="#444b19" d="M169 437l1 1-1-1zm188 0l1 1-1-1z"/><path fill="#779375" d="M170 438l1 1-1-1"/><path fill="#444b19" d="M356 438l1 1-1-1m-186 1l1 1-1-1z"/><path fill="#576e57" d="M284 439l1 1-1-1z"/><path fill="#444b19" d="M355 439l1 1-1-1m-197 1l1 1-1-1m13 0l1 1-1-1z"/><path fill="#576e57" d="M248 440l1 1-1-1z"/><path fill="#444b19" d="M285 440l1 1-1-1z"/><path fill="#779375" d="M343 440l1 1-1-1z"/><path fill="#444b19" d="M354 440l1 1-1-1m-11 1l1 1-1-1m-54 4l1 1-1-1m-30 7c-172.7 40-86.3 20 0 0z"/><path fill="#576e57" d="M290 446l1 1-1-1zm-54 7l1 1-1-1m90 0l1 1-1-1z"/><path fill="#444b19" d="M341 453l1 1-1-1zm-44 1l1 1-1-1zm58 0l1 1-1-1z"/><path fill="#576e57" d="M233 455l1 1-1-1z"/><path fill="#444b19" d="M248 455l1 1-1-1m38 0l1 1-1-1m12 0l1 1-1-1z"/><path fill="#576e57" d="M320 456l1 1-1-1z"/><path fill="#444b19" d="M321 456l1 1-1-1z"/><path fill="#779375" d="M229 457l1 1-1-1z"/><path fill="#444b19" d="M288 457l1 1-1-1z"/><path fill="#779375" d="M319 457l1 1-1-1z"/><path fill="#444b19" d="M289 458l1 1z"/><path fill="#9fc79f" d="M375 459l1 1-1-1z"/><path fill="#576e57" d="M200 462c-133.3 33.3-66.7 16.7 0 0z"/></svg>
\ No newline at end of file diff --git a/static/image/lambda.svg b/static/image/lambda.svg new file mode 100644 index 0000000..76c26c2 --- /dev/null +++ b/static/image/lambda.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg id="svg5" width="61.538mm" height="61.538mm" version="1.1" viewBox="0 0 61.538 61.538" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(-78.308 -101.72)"><path id="path209" d="m121.81 162.75c-0.59549-0.32382-1.0577-1.017-1.3433-2.0144-2.8775-10.051-10.168-30.731-10.714-30.393-0.82553 0.51021-8.7304 17.972-12.047 26.611l-1.2984 3.3824-3.9854 1.1155c-2.192 0.61353-4.3197 1.1155-4.7282 1.1155-0.89868 0-1.8411-1.2469-1.8411-2.4359 0-1.0914 5.0335-10.334 13.717-25.187l6.7334-11.517-1.9168-4.4447c-2.3542-5.4589-3.3324-7.1567-4.9327-8.5618-1.8477-1.6223-3.5422-2.0198-6.5166-1.5286-3.4653 0.57228-3.6407 0.54019-4.5271-0.82819-1.3395-2.068-1.132-2.4558 1.9403-3.6259 10.235-3.8979 16.176-3.6193 20.307 0.95239 1.8593 2.058 3.485 5.524 8.4601 18.037 4.9127 12.356 8.4541 20.548 12.641 29.242 2.2349 4.6405 2.7038 5.8845 2.3448 6.2208-0.44265 0.41458-10.564 4.3963-11.106 4.3689-0.15347-8e-3 -0.6876-0.23629-1.187-0.50783z"/></g></svg> diff --git a/static/image/lobsters.png b/static/image/lobsters.png Binary files differnew file mode 100644 index 0000000..e75cc99 --- /dev/null +++ b/static/image/lobsters.png diff --git a/static/image/oneko.gif b/static/image/oneko.gif Binary files differnew file mode 100644 index 0000000..a009c2c --- /dev/null +++ b/static/image/oneko.gif diff --git a/static/image/opengraph-icon-coq.jpg b/static/image/opengraph-icon-coq.jpg Binary files differnew file mode 100644 index 0000000..33c969a --- /dev/null +++ b/static/image/opengraph-icon-coq.jpg diff --git a/static/image/portrait.png b/static/image/portrait.png Binary files differnew file mode 100644 index 0000000..dfaee55 --- /dev/null +++ b/static/image/portrait.png diff --git a/static/image/profile-picture.jpg b/static/image/profile-picture.jpg Binary files differnew file mode 100644 index 0000000..cd2e07e --- /dev/null +++ b/static/image/profile-picture.jpg diff --git a/static/image/tango-emblem-important.png b/static/image/tango-emblem-important.png Binary files differnew file mode 100644 index 0000000..81c455b --- /dev/null +++ b/static/image/tango-emblem-important.png diff --git a/static/image/tor.svg b/static/image/tor.svg new file mode 100644 index 0000000..ff0f894 --- /dev/null +++ b/static/image/tor.svg @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no" ?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="500" height="500" viewBox="0 0 500 500" xml:space="preserve"> +<defs> +</defs> +<rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect> +<g transform="matrix(0 0 0 0 0 0)" id="4fc577b5-c66b-46cc-95ea-aa650b6bee37" > +</g> +<g transform="matrix(1 0 0 1 250 250)" id="1e5c576a-bbc5-453b-bf9f-d6e354a4a78e" > +<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-250" y="-250" rx="0" ry="0" width="500" height="500" /> +</g> +<g transform="matrix(2.58 0 0 2.58 302.85 86.5)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(171,205,3); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-275.75, -93.3)" d="M 264.513 77.977773 L 259.596 97.50677400000001 C 266.561 83.713773 277.623 73.33477300000001 290.325 64.183773 C 281.038 74.972773 272.57099999999997 85.76277400000001 267.381 96.551774 C 276.12199999999996 84.259773 287.86699999999996 77.43177299999999 301.114 72.92477299999999 C 283.496 88.63077399999999 269.51171999999997 105.48405 258.85871999999995 122.41904999999998 L 250.39171999999994 118.73204999999999 C 251.89271999999994 105.21104999999999 257.0009999999999 91.36277399999999 264.5129999999999 77.97777299999998 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 202.88 314.38)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,252,219); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-237.32, -181.37)" d="M 241.90113 115.14152 L 258.01713 121.82746 C 258.01713 125.92546 257.684 138.42449 260.24651 142.11149 C 287.0494 176.63059 282.54 245.82478 254.817 247.59978 C 212.60044 247.59978 196.5 218.92078 196.5 192.56177000000002 C 196.5 168.52477000000002 225.316 152.54577000000003 242.525 138.34277000000003 C 246.895 134.51877000000002 246.13613 126.06752000000003 241.90113 115.14152000000003 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 329.25 322.02)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(125,70,152); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-285.93, -184.3)" d="M 258.02197 121.58695 L 263.83 124.54977 C 263.284 128.37277 264.103 136.84177 267.926 139.02577 C 284.86199999999997 149.54176999999999 300.84 161.01377 307.123 172.48577 C 329.521 212.91178 291.417 250.33178 258.503 246.78078 C 276.394 233.53278 281.584 206.35577 274.892 176.71877 C 272.161 165.10977 267.926 154.59377 260.414 142.71177 C 257.15979 136.87931 258.29597 129.64595 258.02197 121.58695 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 250 311.11)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-255.61, -180.13)" d="M 255.226 120.58877 L 267.244 122.22776999999999 C 263.69300000000004 133.97277 274.21000000000004 142.16676999999999 277.624 144.07977 C 285.27201 148.31377 292.64701 152.68376999999998 298.52001 158.00977 C 309.58301 168.11577 315.86501 182.31977 315.86501 197.34277 C 315.86501 212.22877 309.03601 226.56877 297.56401 236.12877 C 286.77501 245.14277 271.889 248.96677 257.412 248.96677 C 248.39799999999997 248.96677 240.33999999999997 248.55777 231.59999999999997 245.68877 C 211.66099999999997 238.99677 196.77399999999997 221.92577 195.54499999999996 201.43877 C 194.45199999999997 185.45977 198.00299999999996 173.30477000000002 210.43199999999996 160.60377 C 216.84999999999997 153.91177 229.82499999999996 146.26377 238.70299999999997 140.11777 C 243.07399999999998 137.11277 247.71699999999998 128.64477 238.83899999999997 112.66677000000001 L 240.61499999999998 111.30077000000001 L 253.77158999999997 120.11280000000002 L 242.664 115.53477000000002 C 243.61999999999998 116.90077000000002 246.21499999999997 123.04677000000002 246.762 124.82177000000003 C 247.991 129.87477000000004 247.445 134.79277000000002 246.352 136.97677000000002 C 240.75300000000001 147.08377000000002 231.193 149.81477 224.228 155.55077000000003 C 211.936 165.65677000000002 198.55200000000002 173.71477000000004 200.054 201.43877000000003 C 200.737 215.09577000000004 211.39000000000001 231.75777000000002 227.368 239.54277000000002 C 236.382 243.91377000000003 246.762 245.68877 257.278 246.23477000000003 C 266.701 246.64477000000002 284.72901 241.04477000000003 294.56201000000004 232.85077 C 305.07801000000006 224.11077 310.95101000000005 210.86277 310.95101000000005 197.34277 C 310.95101000000005 183.68477000000001 305.48801000000003 170.71077 295.24501000000004 161.55977000000001 C 289.37201000000005 156.23377000000002 279.67600000000004 149.81477 273.66700000000003 146.39977000000002 C 267.658 142.98577000000003 260.146 133.42577000000003 262.60400000000004 124.27577000000002 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 186.45 348.06)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-230.86, -194.27)" d="M 251.539 140.80177 C 250.30999999999997 147.08477 248.944 158.41977 243.481 162.65377 C 241.159 164.29177 238.837 165.93177 236.379 167.56977 C 226.546 174.26277000000002 216.712 180.54377 212.206 196.65977 C 211.25 200.07477 212.07 203.76177 212.89 207.17577 C 215.34799999999998 217.00877 222.313 227.66177 227.77599999999998 233.94477 C 227.77599999999998 234.21777 228.86899999999997 234.90077 228.86899999999997 235.17377000000002 C 233.37599999999998 240.50077000000002 234.74199999999996 242.00277000000003 251.81299999999996 245.82577 L 251.40299999999996 247.73877000000002 C 241.15999999999997 245.00777000000002 232.69299999999996 242.54977000000002 227.36599999999996 236.40277 C 227.36599999999996 236.26677 226.40999999999997 235.30977000000001 226.40999999999997 235.30977000000001 C 220.67399999999998 228.75377 213.70799999999997 217.82877000000002 211.11399999999998 207.58577000000002 C 210.158 203.48777 209.33899999999997 200.34777000000003 210.43099999999998 196.11277 C 215.07399999999998 179.45177 225.18099999999998 172.89577000000003 235.42399999999998 165.93077000000002 C 237.74599999999998 164.42877000000001 240.47699999999998 163.06177000000002 242.66199999999998 161.28677000000002 C 246.89499999999998 158.14677000000003 249.21599999999998 148.58577000000002 251.539 140.80177000000003 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 224.13 380.53)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-245.38, -206.7)" d="M 255.90625 166.74951 C 256.04325 173.85151 255.35 177.41425999999998 257.125 182.46826 C 258.217 185.47225999999998 261.907 189.56975999999997 263 193.53076 C 264.502 198.85775999999998 266.138 204.72976999999997 266 208.28077 C 266 212.37876 265.74375 220.02326 263.96875 228.21826 C 262.61513 234.98934 259.49552 240.79978999999997 254.25 244.09326 C 248.87673 242.98682 242.56776 241.09805 238.84375 237.90576 C 231.60575 231.62375999999998 225.195 221.11926 224.375 211.96826 C 223.693 204.45727 230.64775 193.37975999999998 240.34375 187.78076 C 248.53775 183.00075999999999 250.44375 177.55300999999997 252.21875 168.81201 C 249.76075 176.46000999999998 247.45225 182.87125999999998 239.53125 186.96826 C 228.05925 192.97726 222.17275 203.06452 222.71875 212.62451 C 223.53775 224.91550999999998 228.46025 233.24400999999997 238.15625 239.93701 C 242.25325 242.80500999999998 249.9075 245.83576 254.6875 246.65576 L 254.6875 246.03076 C 258.31243 245.35188 263.00568 239.39809 265.34375 231.34326 C 267.39275 224.10526 268.2005 214.84125999999998 268.0625 208.96827 C 267.9255 205.55427 266.4195 198.16026 263.6875 191.46826 C 262.1855 187.78125999999997 259.878 184.09450999999999 258.375 181.49951 C 256.738 178.90250999999998 256.72625 173.30451 255.90625 166.74951 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 240.35 411.95)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-251.56, -218.72)" d="M 255.09375 193.53076 C 255.22975 198.31076 257.14975 204.43526999999997 257.96875 210.71827 C 258.65275 215.36226 258.35575 220.02651 258.21875 224.12451 C 258.0838 228.86774 256.50355 237.36668999999998 254.34375 241.49951 C 252.30702 240.56547999999998 251.51081 239.50028999999998 250.1875 237.78076 C 248.5495 235.45875999999998 247.43675 233.13675999999998 246.34375 230.40576 C 245.52475 228.35675999999998 244.56725 226.01175999999998 244.15625 223.28076 C 243.61025 219.18375999999998 243.76325 212.77426 248.40625 206.21827 C 251.95725 201.02826 252.771 200.63351 254 194.62451 C 252.36 199.95050999999998 251.1375 200.49351 247.3125 204.99952 C 243.0795 209.91652 242.375 217.15876 242.375 223.03076 C 242.375 225.48976 243.3555 228.21801 244.3125 230.81201 C 245.4045 233.54401 246.34175 236.26400999999998 247.84375 238.31201 C 250.10171 241.63282999999998 252.99173 243.52122999999997 254.40625 243.87451 C 254.41525 243.87651 254.42825 243.87250999999998 254.43745 243.87451 C 254.46775000000002 243.88151 254.50235 243.90000999999998 254.53115000000003 243.90571 L 254.53115000000003 243.74946 C 257.18097 240.79509 258.77559 237.86012 259.3124 234.90571 C 259.9954 231.35471 260.1524 227.79596 260.5624 223.56196 C 260.9714 220.01096 260.67465000000004 215.22796 259.71865 210.31197 C 258.35365 204.16597000000002 256.04965000000004 197.89971 255.09365000000003 193.53071 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 272.78 340.19)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-264.05, -191.27)" d="M 255.499 135.06577 C 255.636 142.16676999999999 256.182 155.41576999999998 258.094 160.60476999999997 C 258.64 162.37976999999998 263.693 170.16476999999998 267.243 179.58776999999998 C 269.702 186.14377 270.248 192.15276999999998 270.658 193.92776999999998 C 272.297 201.71276999999998 270.248 214.82376999999997 267.516 227.25176999999996 C 266.151 233.94376999999997 261.507 242.27476999999996 256.18100000000004 245.55276999999995 L 255.08900000000003 247.46476999999996 C 258.09400000000005 247.32776999999996 265.468 240.08976999999996 268.06300000000005 231.07576999999995 C 272.434 215.77976999999996 274.20900000000006 208.67776999999995 272.16100000000006 191.74276999999995 C 271.88800000000003 190.10276999999996 271.20500000000004 184.50476999999995 268.61000000000007 178.49476999999996 C 264.78600000000006 169.34376999999995 259.3230000000001 160.60376999999997 258.6410000000001 158.82776999999996 C 257.41100000000006 155.96076999999997 255.77200000000008 143.53276999999997 255.49900000000008 135.06576999999996 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 279.5 327.55)" > +<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-266.65, -186.41)" d="M 258.06151 125.35303 C 257.65636 132.65115 257.548 135.33877 258.913 140.66477 C 260.415 146.53777 268.064 155.00477 271.205 164.70177 C 277.214 183.27577000000002 275.712 207.58577000000002 271.341 226.56877 C 269.70300000000003 233.25977 261.91700000000003 242.95777 254.133 246.09777 L 259.869 247.46377 C 263.01000000000005 247.32677 271.067 239.81577000000001 274.209 231.21177 C 279.261 217.69077 280.218 201.57577 278.169 184.64077 C 278.032 183.00177 275.29999999999995 168.38877 272.70599999999996 162.24277 C 269.018 153.09277 262.462 144.89777 261.78 143.12377 C 260.55199999999996 140.11877 257.85348999999997 133.88015000000001 258.06151 125.35303 z" stroke-linecap="round" /> +</g> +<g transform="matrix(2.58 0 0 2.58 246.81 319.25)" id="rect2556" > +<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-0.275" y="-63.01" rx="0" ry="0" width="0.55" height="126.02" /> +</g> +</svg> diff --git a/static/image/webmention-logo.png b/static/image/webmention-logo.png Binary files differnew file mode 100644 index 0000000..a4ffda1 --- /dev/null +++ b/static/image/webmention-logo.png diff --git a/static/js/comment-reaction.js b/static/js/comment-reaction.js new file mode 100644 index 0000000..1002fa2 --- /dev/null +++ b/static/js/comment-reaction.js @@ -0,0 +1,143 @@ +/* + * comment-reaction.js -- Simple emoji picker and glue for reaction endpoint + * Copyright © 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +function encodeAsFormData(obj) { + // Turn the data object into an array of URL-encoded key/value pairs. + let urlEncodedDataPairs = []; + for (let key in obj) { + urlEncodedDataPairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])); + } + return urlEncodedDataPairs.join('&'); +} + +function postReaction(reaction, callback) { + let xhr = new XMLHttpRequest(); + xhr.open('POST', "/api/comment/react", true); + + // Endpoint expects HTML form data. + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + xhr.onload = function(data) { + switch (xhr.status) { + case 200: + callback(); + break; + case 429: + alert("Failed -- you can react at most once an hour."); + break; + default: + let resp = JSON.parse(xhr.response); + alert("Failed -- " + resp.error); + break; + } + } + xhr.onerror = function(error) { + throw new Error(`Request failed: ${error}`); + } + + xhr.send(encodeAsFormData(reaction)); +} + +function selectorWidgetName(id) { + return `emoji-picker-${id}`; +} + +// Simple Emoji picker widget. +function selectorWidget(id) { + function emojiButton(codepoint) { + let span = document.createElement("span"); + span.innerHTML = String.fromCodePoint(codepoint); + return span; + } + + let selector = document.createElement("div"); + selector.setAttribute("id", selectorWidgetName(id)); + selector.setAttribute("class", "emoji-picker"); + + for (let i = 0x1F600; i < 0x1F64F; i++) { + selector.appendChild(emojiButton(i)); + } + selector.appendChild(emojiButton(0x1F37B)); + selector.appendChild(emojiButton(0x1F440)); + selector.appendChild(emojiButton(0x1F446)); + selector.appendChild(emojiButton(0x1F447)); + selector.appendChild(emojiButton(0x1F448)); + selector.appendChild(emojiButton(0x1F449)); + selector.appendChild(emojiButton(0x1F450)); + selector.appendChild(emojiButton(0x270A)); + selector.appendChild(emojiButton(0x270B)); + selector.appendChild(emojiButton(0x270C)); + selector.appendChild(emojiButton(0x270D)); + for (let i = 0x1F90C; i < 0x1F9FF; i++) { + selector.appendChild(emojiButton(i)); + } + return selector; +} + +// Add a "react" button to every local comment. +window.addEventListener("load", () => { + // First pass to add a separator for the buttons. + let replyButtons = document.querySelectorAll(".comment-reply-button"); + for (let button of replyButtons) { + let parent = button.parentNode; + parent.innerHTML += " - "; + + } + + // Second pass to add the actual "react" button. + replyButtons = document.querySelectorAll(".comment-reply-button"); + for (let button of replyButtons) { + let id = button.getAttribute("data-reply-to-id"); + let reactButton = document.createElement("a"); + reactButton.setAttribute("class", "comment-react-button"); + reactButton.setAttribute("href", "#"); + reactButton.setAttribute("data-react-to-id", id); + reactButton.innerHTML = "react"; + button.parentNode.appendChild(reactButton); + + reactButton.addEventListener('click', (e) => { + // If we already made a selector widget for this comment, let's "hide" the + // one that exists. + let existingSelectorWidget = document.getElementById(selectorWidgetName(id)); + if (existingSelectorWidget !== null) { + existingSelectorWidget.remove(); + e.preventDefault(); + return; + } + + let emojiPicker = selectorWidget(id); + button.parentNode.parentNode.insertBefore(emojiPicker, button.parentNode); + for (let button of emojiPicker.children) { + button.addEventListener('click', (e) => { + postReaction({ + "id": reactButton.getAttribute("data-react-to-id"), + "reaction": button.innerHTML + }, () => { + let reactAcknowledgement = document.createElement("span"); + reactAcknowledgement.innerHTML = "successfully reacted!" + reactButton.replaceWith(reactAcknowledgement); + }); + emojiPicker.remove(); + }); + } + + e.preventDefault(); + }); + } +}); diff --git a/static/js/dynamic-comment-form.js b/static/js/dynamic-comment-form.js new file mode 100644 index 0000000..4be2d52 --- /dev/null +++ b/static/js/dynamic-comment-form.js @@ -0,0 +1,94 @@ +/* + * dynamic-comment-form.js -- Captcha challenge loader + * Copyright © 2022 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +function makeRequest (method, url) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open(method, url); + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 300) { + resolve(xhr.response); + } else { + reject({ + status: xhr.status, + statusText: xhr.statusText + }); + } + }; + xhr.onerror = function () { + reject({ + status: xhr.status, + statusText: xhr.statusText + }); + }; + xhr.send(); + }); +} + +window.addEventListener("load", () => { + let primaryDisplay = document.getElementById("comment-form-primary"); + primaryDisplay.removeAttribute("hidden"); + + let altDisplay = document.getElementById("comment-form-alt"); + altDisplay.remove(); + + let trigger = document.getElementById("captcha-challenge-trigger"); + let triggerBlock = document.getElementById("captcha-trigger-block"); + trigger.addEventListener("click", (event) => { + let captchaField = document.getElementById("comment-captcha"); + + let captchaId = document.getElementById("captcha-id"); + let captchaImage = document.getElementById("captcha-image"); + + trigger.innerHTML = "Please wait..."; + trigger.disabled = true; + + if (captchaId.value === "") { + makeRequest("GET", "/api/challenge/captcha") + .then(function (data) { + // Update form with parsed values. + let challengeData = JSON.parse(data); + captchaId.value = challengeData["challenge-id"]; + captchaImage.src = challengeData["image"]; + + // Unhide fieldset. + captchaField.removeAttribute("hidden"); + + // Hide initial trigger. + triggerBlock.remove(); + }) + .catch(console.err); + } + + event.preventDefault(); + }); +}); + + +window.addEventListener("load", () => { + let header = document.getElementById("comment-form-header"); + let replyTo = document.getElementById("reply-to"); + let buttons = document.querySelectorAll(".comment-reply-button"); + buttons.forEach((button) => { + button.addEventListener("click", (event) => { + header.innerHTML = "Comment form (reply mode)"; + replyTo.value = button.getAttribute("data-reply-to-id"); + }); + }); +}); diff --git a/static/js/oneko.js b/static/js/oneko.js new file mode 100644 index 0000000..b0ba164 --- /dev/null +++ b/static/js/oneko.js @@ -0,0 +1,182 @@ +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +(function oneko() { + const nekoEl = document.createElement("div"); + let nekoPosX = 32; + let nekoPosY = 32; + let mousePosX = 0; + let mousePosY = 0; + let frameCount = 0; + let idleTime = 0; + let idleAnimation = null; + let idleAnimationFrame = 0; + const nekoSpeed = 10; + const spriteSets = { + idle: [[-3, -3]], + alert: [[-7, -3]], + scratch: [ + [-5, 0], + [-6, 0], + [-7, 0], + ], + tired: [[-3, -2]], + sleeping: [ + [-2, 0], + [-2, -1], + ], + N: [ + [-1, -2], + [-1, -3], + ], + NE: [ + [0, -2], + [0, -3], + ], + E: [ + [-3, 0], + [-3, -1], + ], + SE: [ + [-5, -1], + [-5, -2], + ], + S: [ + [-6, -3], + [-7, -2], + ], + SW: [ + [-5, -3], + [-6, -1], + ], + W: [ + [-4, -2], + [-4, -3], + ], + NW: [ + [-1, 0], + [-1, -1], + ], + }; + function create() { + nekoEl.id = "oneko"; + nekoEl.style.width = "32px"; + nekoEl.style.height = "32px"; + nekoEl.style.position = "fixed"; + nekoEl.style.backgroundImage = "url('/static/image/oneko.gif')"; + nekoEl.style.imageRendering = "pixelated"; + nekoEl.style.left = "16px"; + nekoEl.style.top = "16px"; + + document.body.appendChild(nekoEl); + + document.onmousemove = (event) => { + mousePosX = event.clientX; + mousePosY = event.clientY; + }; + + window.onekoInterval = setInterval(frame, 100); + } + + function setSprite(name, frame) { + const sprite = spriteSets[name][frame % spriteSets[name].length]; + nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${ + sprite[1] * 32 + }px`; + } + + function resetIdleAnimation() { + idleAnimation = null; + idleAnimationFrame = 0; + } + + function idle() { + idleTime += 1; + + // every ~ 20 seconds + if ( + idleTime > 10 && + Math.floor(Math.random() * 200) == 0 && + idleAnimation == null + ) { + idleAnimation = ["sleeping", "scratch"][ + Math.floor(Math.random() * 2) + ]; + } + + switch (idleAnimation) { + case "sleeping": + if (idleAnimationFrame < 8) { + setSprite("tired", 0); + break; + } + setSprite("sleeping", Math.floor(idleAnimationFrame / 4)); + if (idleAnimationFrame > 192) { + resetIdleAnimation(); + } + break; + case "scratch": + setSprite("scratch", idleAnimationFrame); + if (idleAnimationFrame > 9) { + resetIdleAnimation(); + } + break; + default: + setSprite("idle", 0); + return; + } + idleAnimationFrame += 1; + } + + function frame() { + frameCount += 1; + const diffX = nekoPosX - mousePosX; + const diffY = nekoPosY - mousePosY; + const distance = Math.sqrt(diffX ** 2 + diffY ** 2); + + if (distance < nekoSpeed || distance < 48) { + idle(); + return; + } + + idleAnimation = null; + idleAnimationFrame = 0; + + if (idleTime > 1) { + setSprite("alert", 0); + // count down after being alerted before moving + idleTime = Math.min(idleTime, 7); + idleTime -= 1; + return; + } + + direction = diffY / distance > 0.5 ? "N" : ""; + direction += diffY / distance < -0.5 ? "S" : ""; + direction += diffX / distance > 0.5 ? "W" : ""; + direction += diffX / distance < -0.5 ? "E" : ""; + setSprite(direction, frameCount); + + nekoPosX -= (diffX / distance) * nekoSpeed; + nekoPosY -= (diffY / distance) * nekoSpeed; + + nekoEl.style.left = `${nekoPosX - 16}px`; + nekoEl.style.top = `${nekoPosY - 16}px`; + } + + create(); +})(); diff --git a/static/js/proof-of-work.js b/static/js/proof-of-work.js new file mode 100644 index 0000000..b4b5c57 --- /dev/null +++ b/static/js/proof-of-work.js @@ -0,0 +1,109 @@ +/* + * proof-of-work.js -- Alternative captcha based on SHA-256 proof-of-work. + * Copyright © 2022 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +function makeid(length) { + let result = ''; + let alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for (let i = 0; i < length; i++) { + result += alphabet.charAt(Math.floor(Math.random() * alphabet.length)); + } + return result; +} + +async function digestMessage(message) { + const msgUtf8 = new TextEncoder().encode(message); + const hashBuffer = await crypto.subtle.digest('SHA-256', msgUtf8); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + return hashHex; +} + +async function findPrefix(hardness, nonce) { + while (true) { + let prefix = makeid(32); + let digestHex = await digestMessage(prefix + nonce); + if (digestHex.startsWith("0".repeat(hardness))) { + return prefix; + } + } +} + +function makeRequest (method, url) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open(method, url); + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 300) { + resolve(xhr.response); + } else { + reject({ + status: xhr.status, + statusText: xhr.statusText + }); + } + }; + xhr.onerror = function () { + reject({ + status: xhr.status, + statusText: xhr.statusText + }); + }; + xhr.send(); + }); +} + +function raceEndpoint() { + return new Promise(function (resolve, reject) { + makeRequest("GET", "/api/challenge/proof-of-work") + .then(function (data) { + let challengeData = JSON.parse(data); + findPrefix(challengeData.hardness, challengeData.nonce) + .then((prefix) => { resolve([prefix, challengeData["challenge-id"]]) } ); + }) + .catch(reject); + }); +} + +window.addEventListener("load", () => { + let trigger = document.getElementById("pow-trigger"); + trigger.removeAttribute("hidden"); + trigger.addEventListener("click", (e) => { + trigger.innerHTML = "Please wait..."; + trigger.disabled = true; + raceEndpoint() + .then((result) => { + let resultField = document.getElementById("captcha-alt"); + let challengeIdField = document.getElementById("captcha-alt-id"); + resultField.value = result[0]; + challengeIdField.value = result[1]; + trigger.innerHTML = "Proof-of-Work completed successfully!"; + trigger.disabled = true; + + // Hide the primary captcha, too, to make it clear that it isn't necessary to complete. + let primaryChallenge = document.getElementById("captcha-challenge-primary"); + primaryChallenge.hidden = true; + }) + .catch((err) => { + console.log(err); + trigger.innerHTML = "Proof-of-Work failed!"; + trigger.disabled = true; + }); + e.preventDefault(); + }) +}); diff --git a/static/js/rsvp.js b/static/js/rsvp.js new file mode 100644 index 0000000..06cdb66 --- /dev/null +++ b/static/js/rsvp.js @@ -0,0 +1,206 @@ +/* + * rsvp.js -- Simple web-based RSVP tool. + * Copyright © 2022 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +// Common nodes. +const guestView = document.getElementById("guest-view"); +const addEntry = document.getElementById("add-entry"); +const removeEntry = document.getElementById("remove-entry"); +const submitButton = document.getElementById("submit-form"); +const form = document.getElementById("rsvp-input"); + +// Parse out the HTML form into something we can send to the backend. +function submitForm() { + let data = new FormData(form); + if (data.get("name").trim().length == 0) { + alert("You have to provide something for \"Name\"."); + return; + } else if (data.get("rsvp") === null) { + alert("You have to choose an option for \"RSVP Status\"."); + return; + } + + let res = { guests: [] }; + if (urlParams.get("i")) { + res["id"] = urlParams.get("i"); + } else if (urlParams.get("r")) { + res["update"] = urlParams.get("r"); + } + for (let pair of data.entries()) { + if (pair[0].startsWith("guest-")) { + res.guests.push(pair[1]); + } else if (pair[0] === "rsvp") { + const radioButtons = document.querySelectorAll('input[name="rsvp"]'); + for (const radioButton of radioButtons) { + if (radioButton.checked) { + res.rsvp = radioButton.id; + break; + } + } + } else { + res[pair[0]] = pair[1]; + } + } + res.guests = res.guests.join(","); + + let xhr = new XMLHttpRequest(); + xhr.open("POST", `${location.protocol}//${location.host}/api/rsvp`); + xhr.send(JSON.stringify(res)); + xhr.onload = function(data) { + if (xhr.status != 200) { + alert("Failed to submit form."); + } else { + document.getElementById("rsvp").innerHTML = ""; + let resp = JSON.parse(xhr.response); + let elem = document.createElement("p"); + elem.innerHTML = "Thanks for registering! Please bookmark or save the following link:"; + document.getElementById("rsvp").appendChild(elem); + + elem = document.createElement("a"); + elem.href = [location.protocol, '//', location.host, location.pathname].join('') + `?r=${resp.receipt}`; + elem.textContent = elem.href; + document.getElementById("rsvp").appendChild(elem); + + elem = document.createElement("p"); + elem.innerHTML = "This will enable you to edit your RSVP later."; + document.getElementById("rsvp").appendChild(elem); + } + } + xhr.onerror = function(error) { + throw new Error(`Request failed: ${error}`); + } +} + +// Immediately send off an XHR to load info from the backend. +const urlParams = new URLSearchParams(window.location.search); +if (!urlParams.get("i") && !urlParams.get("r")) { + document.getElementById("loading").remove(); + let elem = document.createElement("p"); + elem.innerHTML = "No invitation code provided."; + document.getElementById("rsvp").appendChild(elem); +} else { + let xhr = new XMLHttpRequest(); + if (urlParams.get("i")) { + xhr.open("GET", `${location.protocol}//${location.host}/api/rsvp/event-info?i=${urlParams.get("i")}`); + } else { + xhr.open("GET", `${location.protocol}//${location.host}/api/rsvp/event-info?r=${urlParams.get("r")}`); + } + xhr.send(); + xhr.onload = function(data) { + document.getElementById("loading").remove(); + if (xhr.status != 200) { + let elem = document.createElement("p"); + elem.innerHTML = "Invalid invitation code."; + document.getElementById("rsvp").appendChild(elem); + } else { + document.getElementById("rsvp-input").hidden = false; + + let response = JSON.parse(xhr.response); + + let elem = document.createElement("h1"); + elem.innerHTML = response.title; + document.getElementById("event-info").appendChild(elem); + + if (response.image !== "#f") { + elem = document.createElement("img"); + elem.src = `data:image/png;base64,${response.image}`; + elem.style = "float: right; max-size: 200px; margin: 16px;"; + document.getElementById("event-info").appendChild(elem); + } + + elem = document.createElement("p"); + elem.innerHTML = `Where: ${response.location}`; + document.getElementById("event-info").appendChild(elem); + + elem = document.createElement("p"); + elem.innerHTML = `When: ${response.date}`; + document.getElementById("event-info").appendChild(elem); + + elem = document.createElement("p"); + elem.innerHTML = response.description; + document.getElementById("event-info").appendChild(elem); + + if (response.hasOwnProperty("rsvps")) { + elem = document.createElement("h2"); + elem.innerHTML = "Current RSVPs"; + document.getElementById("rsvp-global").appendChild(elem); + + elem = document.createElement("table"); + document.getElementById("rsvp-global").appendChild(elem); + + for (let rsvp of response.rsvps) { + let container = document.createElement("tr"); + let field = document.createElement("td"); + field.textContent = rsvp.name; + container.append(field); + field = document.createElement("td"); + field.textContent = rsvp.email; + container.append(field); + field = document.createElement("td"); + field.textContent = rsvp.guests.replaceAll(',', ', '); + container.append(field); + field = document.createElement("td"); + field.textContent = rsvp.attending; + container.append(field); + elem.appendChild(container); + } + } + + // Pre-filled info if this is an edit. + if (response.hasOwnProperty("name")) { + document.getElementById("name").value = response.name; + } + if (response.hasOwnProperty("email")) { + document.getElementById("email").value = response.email; + } + if (response.hasOwnProperty("attending")) { + document.getElementById(response.attending).checked = true; + } + if (response.hasOwnProperty("guests") && response.guests.trim() != "") { + let i = 0; + for (let guest of response.guests.split(",")) { + let elem = document.createElement("input"); + elem.type = "text"; + elem.name = `guest-${i}`; + elem.value = guest; + guestView.appendChild(elem); + i++; + } + } + } + } + xhr.onerror = function(error) { + throw new Error(`Request failed: ${error}`); + } +} + +// Attach event listeners to the UI. +submitButton.addEventListener("click", submitForm); +submitButton.addEventListener("submit", submitForm); +addEntry.addEventListener("click", () => { + let i = guestView.childNodes.length; + let elem = document.createElement("input"); + elem.type = "text"; + elem.name = `guest-${i}`; + guestView.appendChild(elem); +}); +removeEntry.addEventListener("click", () => { + if (guestView.lastChild) { + guestView.lastChild.remove(); + } +}); diff --git a/static/js/section-folds.js b/static/js/section-folds.js new file mode 100644 index 0000000..9440264 --- /dev/null +++ b/static/js/section-folds.js @@ -0,0 +1,66 @@ +/* + * section-folds.js -- Parse out sections to hide behind an "unfold" dialog + * Copyright © 2022 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +function handleFolds() { + function displayFold(fold, foldText, frontMatterText) { + foldText.innerHTML = `⬇️ ${frontMatterText}`; + fold.classList.remove("fold-hidden"); + + } + + function hideFold(fold, foldText, frontMatterText) { + foldText.innerHTML = `➡️ Folded ${frontMatterText}`; + fold.classList.add("fold-hidden"); + } + + let folds = document.querySelectorAll(".section-fold"); + + for (let fold of folds) { + // The first thing we'll do is generate the "front matter" for the fold, so + // that the reader knows what lies beneath the fold. + let frontMatterText = `Section on "${fold.dataset.name}"`; + let foldFrontMatter = document.createElement("div"); + + // The text will be inside of an anchor, which we'll hook onto using an + // event listener. + let foldAnchor = document.createElement("a"); + let foldText = document.createElement("p"); + foldAnchor.appendChild(foldText); + fold.parentNode.insertBefore(foldAnchor, fold.nextSibling); + + // The fold will start off in the "hidden" state. + let toggle = false; + hideFold(fold, foldText, frontMatterText); + + // But clicking on the "front matter" will cause the state of the fold + // to toggle, first to "visible", and then back to "hidden". + foldAnchor.addEventListener('click', () => { + toggle = !toggle; + if (toggle) { + displayFold(fold, foldText, frontMatterText); + } else { + hideFold(fold, foldText, frontMatterText); + } + }); + } + +} + +// Defer parsing all of the "folds" until the document has completely loaded. +document.addEventListener("DOMContentLoaded", handleFolds); diff --git a/static/js/webmention.js b/static/js/webmention.js new file mode 100644 index 0000000..d280d4d --- /dev/null +++ b/static/js/webmention.js @@ -0,0 +1,229 @@ +/* + * webmention.js -- Fetch and display mentions from webmention.io. + * Copyright © 2019 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * <http://www.gnu.org/licenses/>. + */ + +function buildApiUri() { + const ref = "jakob.space"; + const slug = /[^\/]*(?=\.html)/.exec(window.location.href); + const endpoints = [ + `http://${ref}/${slug}.html`, + `https://${ref}/${slug}.html`, + `http://${ref}/blog/${slug}.html`, + `https://${ref}/blog/${slug}.html`, + ]; + return ["https://webmention.io/api/mentions.jf2?per-page=200&page=0"].concat( + endpoints.map((endpoint) => { + return `target[]=${endpoint}`; + }).join("&") + ).join(""); +} + +function getData(url, callback) { + if (fetch) { + fetch(url).then(function(response) { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + return Promise.reject(new Error(`Request failed: ${response.statusText}`)); + } + }).then(function(response) { + return response.json(); + }).then(callback); + } else { + let xhr = new XMLHttpRequest(); + xhr.onload = function(data) { + callback(JSON.parse(data)); + } + xhr.onerror = function(error) { + throw new Error(`Request failed: ${error}`); + } + } +} + +function makeComment(comment) { + const strip = (uri) => { + const sep = "://"; + return uri.substring(uri.indexOf(sep) + sep.length); + }; + + const element = (type, attributes) => { + let res = document.createElement(type); + for (const attribute in attributes) { + res.setAttribute(attribute, attributes[attribute]); + } + return res; + }; + + // Create the h-card section. + let avatar = element("img", { + "class": "u-photo", + "src": comment.url.startsWith("https://lobste.rs/") + ? "/static/image/lobsters.png" + : comment.author.photo || "/static/image/default-icon.png" + }); + + let authorName = element("a", { + "class": "p-name u-url", + "href": comment.author.url + }); + authorName.textContent = comment.author.name; + + let authorURI = element("a", { + "class": "author_url", + "href": comment.author.url + }); + authorURI.textContent = strip(comment.author.url); + + let hCardContainer = element("div", { + "class": "p-author h-card author" + }); + hCardContainer.appendChild(avatar); + hCardContainer.appendChild(authorName); + hCardContainer.appendChild(authorURI); + + // Create the content section. + let contentContainer = element("div", { + "class": "e-content p-name comment-content", + }); + + if (comment.hasOwnProperty("content")) { + contentContainer.textContent = comment.content.text; + } else { + let em = element("em", {}); + if (comment.hasOwnProperty("wm-property")) { + if ("repost-of" === comment["wm-property"]) { + em.textContent = "Reposted this!"; + } else if ("like-of" === comment["wm-property"]) { + em.textContent = "Favorited this!"; + } else { + em.textContent = "No text provided :("; + } + } else { + em.textContent = "No text provided :("; + } + contentContainer.appendChild(em); + } + + // Create the metaline section. + let pubTime = comment.published || comment["wm-received"]; + let time = element("time", { + "class": "dt-published", + "datetime": pubTime, + }); + time.textContent = (new Date(pubTime)).toString(); + + let linkBack = element("a", { + "class": "u-url", + "href": comment.url + }); + linkBack.appendChild(time); + + let metalineContainer = element("div", { + "class": "metaline" + }); + metalineContainer.appendChild(linkBack); + + let sourceIcon = document.createElement("img"); + sourceIcon.classList.add("comment-source-identifier") + sourceIcon.src = "/static/image/webmention-logo.png"; + + // Put it all together. + let wrapper = element("li", { + "class": "p-comment h-cite comment comment-source-webmention", + }); + wrapper.appendChild(sourceIcon); + wrapper.appendChild(hCardContainer); + wrapper.appendChild(contentContainer); + wrapper.appendChild(metalineContainer); + + return wrapper; +} + +function makeInteraction(ent) { + const strip = (uri) => { + const sep = "://"; + return uri.substring(uri.indexOf(sep) + sep.length); + }; + + const element = (type, attributes) => { + let res = document.createElement(type); + for (const attribute in attributes) { + res.setAttribute(attribute, attributes[attribute]); + } + return res; + }; + + // Create the h-card section. + let avatar = element("img", { + "class": "u-photo", + "src": ent.url.startsWith("https://lobste.rs/") + ? "/static/image/lobsters.png" + : ent.author.photo || "/static/image/default-icon.png" + }); + let avatarContainer = element("a", { + "href": ent.author.url + }); + avatarContainer.appendChild(avatar); + + // Create the content section. + let contentContainer = element("div", { + "class": "e-content p-name comment-content", + }); + let em = element("em", {}); + if ("repost-of" === ent["wm-property"]) { + em.textContent = "Reposted this!"; + } else if ("like-of" === ent["wm-property"]) { + em.textContent = "Favorited this!"; + } else { + return null; + } + contentContainer.appendChild(em); + + let sourceIcon = document.createElement("img"); + sourceIcon.classList.add("comment-source-identifier") + sourceIcon.src = "/static/image/webmention-logo.png"; + + // Put it all together. + let wrapper = element("li", { + "class": "p-comment h-cite interaction comment-source-webmention", + }); + wrapper.appendChild(avatarContainer); + wrapper.appendChild(contentContainer); + wrapper.appendChild(sourceIcon); + + return wrapper; +} + +function makeEntry(ent) { + if (ent.hasOwnProperty("wm-property")) { + if (ent.hasOwnProperty("content") && ent["wm-property"] !== "repost-of") { + return makeComment(ent); + } else { + return makeInteraction(ent); + } + } + return null; +} + +getData(buildApiUri(), (json) => { + json.children + .map(makeEntry) + .filter((x) => x !== null) + .forEach((elem) => { + document.getElementById("webmention-container").appendChild(elem); + })}); |