timconspicuous.neocities.org

Initial commit

+456
+5
.gitignore
··· 1 + _cache 2 + _site 3 + deno.lock 4 + .vscode 5 + .env
+83
_cms.ts
··· 1 + import lumeCMS from "lume/cms/mod.ts"; 2 + 3 + const cms = lumeCMS(); 4 + 5 + cms.document( 6 + "home: The profile page", 7 + "src:index.yml", 8 + [ 9 + { 10 + type: "hidden", 11 + name: "layout", 12 + value: "layouts/home.vto", 13 + }, 14 + { 15 + type: "object", 16 + name: "header", 17 + description: "The header of the page", 18 + fields: [ 19 + "title: text", 20 + "description: markdown", 21 + "avatar: file", 22 + ], 23 + }, 24 + { 25 + type: "object", 26 + name: "metas", 27 + description: "Data for the meta tags", 28 + fields: [ 29 + "title: text", 30 + "description: text", 31 + "image: text", 32 + "twitter: text", 33 + "generator: checkbox", 34 + ], 35 + }, 36 + { 37 + name: "links", 38 + type: "object-list", 39 + description: "The list of links.", 40 + fields: [ 41 + { 42 + type: "text", 43 + name: "type", 44 + description: 45 + "The type of link. It uses the icons and colors from https://simpleicons.org/. For example, 'github', 'instagram', etc.", 46 + options: [ 47 + "github", 48 + "instagram", 49 + "linkedin", 50 + "x", 51 + "youtube", 52 + "facebook", 53 + "tiktok", 54 + "patreon", 55 + "paypal", 56 + "mastodon", 57 + "discord", 58 + "spotify", 59 + "opencollective", 60 + "twitch", 61 + ], 62 + }, 63 + "text: text", 64 + "href: text", 65 + "only_icon: checkbox", 66 + ], 67 + }, 68 + { 69 + name: "extra_head", 70 + type: "code", 71 + description: "Extra content to include in the <head> tag", 72 + }, 73 + { 74 + name: "footer", 75 + type: "markdown", 76 + description: "The footer of the page", 77 + }, 78 + ], 79 + ); 80 + 81 + cms.upload("uploads: Uploaded files", "src:*{.jpg,.svg}"); 82 + 83 + export default cms;
+10
_config.ts
··· 1 + import lume from "lume/mod.ts"; 2 + import plugins from "./plugins.ts"; 3 + 4 + const site = lume({ 5 + src: "./src", 6 + }); 7 + 8 + site.use(plugins()); 9 + 10 + export default site;
+23
deno.json
··· 1 + { 2 + "imports": { 3 + "lume/": "https://deno.land/x/lume@v2.4.2/", 4 + "lume/cms/": "https://cdn.jsdelivr.net/gh/lumeland/cms@0.7.3/" 5 + }, 6 + "tasks": { 7 + "lume": "echo \"import 'lume/cli.ts'\" | deno run -A -", 8 + "build": "deno task lume", 9 + "serve": "deno task lume -s" 10 + }, 11 + "compilerOptions": { 12 + "types": [ 13 + "lume/types.ts" 14 + ] 15 + }, 16 + "exclude": [ 17 + "./_site" 18 + ], 19 + "fmt": { 20 + "useTabs": true, 21 + "indentWidth": 4 22 + } 23 + }
+25
mod.ts
··· 1 + import plugins from "./plugins.ts"; 2 + 3 + import "lume/types.ts"; 4 + 5 + export default function () { 6 + return (site: Lume.Site) => { 7 + // Configure the site 8 + site.use(plugins()); 9 + 10 + // Add remote files 11 + const files = [ 12 + "_includes/css/header.css", 13 + "_includes/css/link.css", 14 + "_includes/layouts/base.vto", 15 + "index.yml", 16 + "styles.css", 17 + "favicon.svg", 18 + "avatar.jpg", 19 + ]; 20 + 21 + for (const file of files) { 22 + site.remoteFile(file, import.meta.resolve(`./src/${file}`)); 23 + } 24 + }; 25 + }
+30
plugins.ts
··· 1 + import "lume/types.ts"; 2 + import Color from "npm:colorjs.io@0.5.2"; 3 + import simpleIcons from "https://deno.land/x/lume_icon_plugins@v0.1.1/simpleicons.ts"; 4 + import basePath from "lume/plugins/base_path.ts"; 5 + import favicon from "lume/plugins/favicon.ts"; 6 + import metas from "lume/plugins/metas.ts"; 7 + import postcss from "lume/plugins/postcss.ts"; 8 + import transformImages from "lume/plugins/transform_images.ts"; 9 + 10 + /** Configure the site */ 11 + export default function () { 12 + return (site: Lume.Site) => { 13 + site.use(postcss()) 14 + .use(metas()) 15 + .use(favicon()) 16 + .use(basePath()) 17 + .mergeKey("extra_head", "stringArray") 18 + .use(transformImages()) 19 + .use(simpleIcons()); 20 + 21 + site.data("textColor", (hex: string) => { 22 + const color = new Color(`#${hex}`); 23 + const onWhite = Math.abs(color.contrastWCAG21("white")); 24 + const onBlack = Math.abs(color.contrastWCAG21("black")); 25 + return (onWhite + 0.5) > onBlack ? "white" : "black"; 26 + }); 27 + 28 + site.copy([".jpg", ".webp", ".png"]); 29 + }; 30 + }
+36
src/_includes/css/header.css
··· 1 + .header { 2 + font: var(--font-body); 3 + margin-bottom: min(5vh, 100px); 4 + color: var(--color-text); 5 + 6 + p { 7 + margin: 0; 8 + text-wrap: balance; 9 + 10 + + p { 11 + margin-top: .5em; 12 + } 13 + } 14 + } 15 + 16 + .header-avatar { 17 + border-radius: 50%; 18 + aspect-ratio: 1; 19 + object-fit: cover; 20 + object-position: center center; 21 + width: 200px; 22 + max-width: 50vw; 23 + } 24 + 25 + .header-title { 26 + font: var(--font-title); 27 + letter-spacing: var(--font-title-spacing); 28 + margin: .5em 0 0; 29 + color: var(--color-base); 30 + } 31 + 32 + .header-theme { 33 + position: absolute; 34 + top: 1rem; 35 + right: 1.5rem; 36 + }
+65
src/_includes/css/link.css
··· 1 + .link-list { 2 + list-style: none; 3 + margin: 0; 4 + padding: 0; 5 + display: grid; 6 + row-gap: 10px; 7 + 8 + .button { 9 + display: flex; 10 + font: var(--font-body-bold); 11 + transition: transform 200ms; 12 + border: solid 1px #00000022; 13 + 14 + &:hover { 15 + transform: scale(1.05); 16 + box-shadow: 0 2px 10px -8px #0009; 17 + } 18 + } 19 + .button:not(.is-primary) { 20 + background: var(--bg-color); 21 + color: var(--text-color); 22 + } 23 + 24 + svg { 25 + width: 20px; 26 + height: 20px; 27 + fill: currentColor; 28 + } 29 + } 30 + 31 + [data-theme="dark"] { 32 + .link-list .button { 33 + border: solid 1px #FFFFFF16; 34 + } 35 + } 36 + 37 + .icon-list { 38 + list-style: none; 39 + margin: 0 0 min(5vh, 100px); 40 + padding: 0; 41 + display: flex; 42 + gap: 10px; 43 + justify-content: center; 44 + 45 + svg { 46 + width: 20px; 47 + height: 20px; 48 + fill: currentColor; 49 + } 50 + 51 + .button { 52 + display: flex; 53 + font: var(--font-body-bold); 54 + transition: transform 200ms; 55 + 56 + &:hover { 57 + transform: scale(1.05); 58 + box-shadow: 0 2px 10px -8px #0009; 59 + } 60 + } 61 + .button:not(.is-primary) { 62 + background: var(--bg-color); 63 + color: var(--text-color); 64 + } 65 + }
+84
src/_includes/layouts/base.vto
··· 1 + <!DOCTYPE html> 2 + 3 + <html lang="{{ it.lang }}"> 4 + <head> 5 + <meta charset="utf-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>{{ header.title }}</title> 8 + 9 + <meta name="supported-color-schemes" content="light dark"> 10 + <meta name="theme-color" content="hsl(220, 20%, 100%)" media="(prefers-color-scheme: light)"> 11 + <meta name="theme-color" content="hsl(220, 20%, 10%)" media="(prefers-color-scheme: dark)"> 12 + 13 + <link rel="stylesheet" href="/styles.css"> 14 + <link rel="icon" type="image/png" sizes="32x32" href="/favicon.png"> 15 + <link rel="canonical" href="{{ url |> url(true) }}"> 16 + 17 + {{ it.extra_head?.join("\n") }} 18 + </head> 19 + <body> 20 + <main> 21 + <header class="header"> 22 + <script> 23 + let theme = localStorage.getItem("theme") || (window.matchMedia("(prefers-color-scheme: dark)").matches 24 + ? "dark" 25 + : "light"); 26 + document.documentElement.dataset.theme = theme; 27 + function changeTheme() { 28 + theme = theme === "dark" ? "light" : "dark"; 29 + localStorage.setItem("theme", theme); 30 + document.documentElement.dataset.theme = theme; 31 + } 32 + </script> 33 + <button class="button header-theme" onclick="changeTheme()"> 34 + <span class="icon">◐</span> 35 + </button> 36 + 37 + <img class="header-avatar" src="{{ header.avatar }}" alt="Avatar" transform-images="webp avif 200@2"> 38 + <h1 class="header-title">{{ header.title }}</h1> 39 + {{ header.description |> md }} 40 + </header> 41 + 42 + {{> const icons = links.filter((link) => link.only_icon) }} 43 + 44 + {{ if icons.length }} 45 + <ul class="icon-list"> 46 + {{ for link of icons }} 47 + {{ set hex = link.type |> simpleicons("hex") }} 48 + <li> 49 + <a 50 + href="{{ link.href }}" 51 + class="button" 52 + style='--bg-color:{{ link.hex || `#${hex || "fff" }` }}; --text-color:{{ link.textColor || textColor(hex || "fff") }}' 53 + title="{{ link.text }}" 54 + > 55 + {{ link.type |> simpleicons }} 56 + </a> 57 + </li> 58 + {{ /for }} 59 + </ul> 60 + {{ /if }} 61 + 62 + <ul class="link-list"> 63 + {{ for link of links.filter((link) => !link.only_icon) }} 64 + {{ set hex = link.type |> simpleicons("hex") }} 65 + <li> 66 + <a 67 + href="{{ link.href }}" 68 + class="button" 69 + style='--bg-color:{{ link.hex || `#${hex || "fff" }` }}; --text-color:{{ link.textColor || textColor(hex || "fff") }}' 70 + > 71 + {{ link.type |> simpleicons }} 72 + {{ link.text }} 73 + </a> 74 + </li> 75 + {{ /for }} 76 + </ul> 77 + </main> 78 + {{ if footer }} 79 + <footer> 80 + {{ footer |> md }} 81 + </footer> 82 + {{ /if }} 83 + </body> 84 + </html>
src/avatar.jpg

This is a binary file and will not be displayed.

+21
src/favicon.svg
··· 1 + <svg width="204" height="204" viewBox="0 0 204 204" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <style> 3 + path { fill: black } 4 + rect { fill: white } 5 + @media (prefers-color-scheme: dark) { 6 + path { fill: white } 7 + rect { fill: black } 8 + } 9 + </style> 10 + <g clip-path="url(#clip0_347_441)"> 11 + <path d="M109.101 78.5092C94.3635 64.9692 81.938 53.5531 88.4027 27.2952C60.4821 61.9286 59.3798 73.4094 70.9216 102.953C78.555 122.515 68.0654 137.935 57.754 153.094C52.4869 160.838 47.2663 168.512 44.5314 176.636C30.8216 160.139 27.9976 144.342 30.8216 115.936C25.4552 132.474 17.7529 144.245 10.3272 155.594C10.1898 155.804 10.0525 156.014 9.91525 156.224L7.9476 159.228L6.01638 162.233C3.92324 165.524 1.89646 168.851 0 172.318V204H204V117.299C193.619 102.125 184.654 85.3552 184.654 58.6146C164.214 77.2349 161.12 96.4956 158.242 114.414C156.018 128.254 153.923 141.294 144.062 152.618C144.062 110.63 125.202 93.3021 109.101 78.5092Z"/> 12 + <path d="M90.0789 0V0.0182087C90.085 0.0255289 90.0912 0.0328444 90.0974 0.0401554C96.8483 14.6686 97.1207 24.0354 97.3552 32.1004C97.7228 44.7449 97.9974 54.1894 123.001 75.6946C119.59 64.5616 120.297 57.8549 120.947 51.6871C121.623 45.27 122.239 39.4363 118.091 29.808C113.247 18.5601 108.662 15.3807 103.113 11.5325C99.3461 8.92056 95.1351 6.00049 90.0974 0.0401554C90.0912 0.0267747 90.0851 0.0133896 90.0789 0Z"/> 13 + <path d="M58.8515 102.198C50.325 82.8507 48.7126 60.8179 56.1277 53.2612C37.5261 63.5583 23.4975 87.8308 33.7366 106.167C43.9848 124.504 46.1711 130.167 44.0759 154.002C44.7992 152.927 45.5196 151.865 46.2331 150.812C57.4069 134.329 66.8662 120.375 58.8515 102.198Z"/> 14 + <path d="M138.224 35.0982C135.582 32.9394 132.543 30.4559 129.049 27.4227C135.015 41.9862 133.894 52.3277 132.906 61.453C131.476 74.6505 130.321 85.3043 151.277 102.507C149.434 91.4006 151.127 84.2178 152.725 77.4388C154.042 71.848 155.295 66.5319 154.447 59.5159C152.933 47.1177 148.706 43.6636 138.224 35.0982Z"/> 15 + </g> 16 + <defs> 17 + <clipPath id="clip0_347_441"> 18 + <rect width="204" height="204"/> 19 + </clipPath> 20 + </defs> 21 + </svg>
+35
src/index.yml
··· 1 + layout: layouts/base.vto 2 + header: 3 + title: timconspicuous 4 + description: 5 + avatar: /avatar.jpg 6 + metas: 7 + title: =header.title 8 + description: =header.description 9 + image: =header.avatar 10 + generator: true 11 + twitter: '' 12 + links: 13 + - type: bluesky 14 + text: Bluesky 15 + href: 'https://bsky.app/profile/timconspicuous.neocities.org' 16 + - type: letterboxd 17 + text: 'Letterboxd' 18 + href: 'https://letterboxd.com/timconspicuous' 19 + - type: '' 20 + text: BookWyrm 21 + href: 'https://bookwyrm.social/user/timconspicuous' 22 + - type: lichess 23 + text: Lichess 24 + href: 'https://lichess.org/@/timconspicuous' 25 + - type: discord 26 + text: Discord 27 + href: 'https://discordapp.com/users/timconspicuous' 28 + - type: spotify 29 + text: Spotify 30 + href: 'https://open.spotify.com/user/iafsfv7j85qcxqhnygkl8xuds' 31 + - type: tumblr 32 + text: Tumblr 33 + href: 'https://www.tumblr.com/timconspicuous' 34 + footer: "Powered by [Lume](https://lume.land) & [SimpleMe](https://github.com/lumeland/theme-simple-me) theme" 35 + extra_head: ''
+39
src/styles.css
··· 1 + /* Lume's design system */ 2 + @import "https://unpkg.com/@lumeland/ds@0.5.2/ds.css"; 3 + 4 + /* Custom components */ 5 + @import "css/header.css"; 6 + @import "css/link.css"; 7 + 8 + body { 9 + display: grid; 10 + grid-template-columns: minmax(0, 500px); 11 + grid-template-rows: 1fr auto; 12 + min-height: 100vh; 13 + text-align: center; 14 + padding: max(20px, 5vh) 20px; 15 + row-gap: 20px; 16 + justify-content: center; 17 + align-content: center; 18 + } 19 + 20 + main { 21 + align-self: center; 22 + } 23 + 24 + footer { 25 + font: var(--font-small); 26 + color: var(--color-dim); 27 + 28 + > * { 29 + margin: 0; 30 + } 31 + 32 + > * + * { 33 + margin-top: 1em; 34 + } 35 + 36 + a { 37 + color: inherit; 38 + } 39 + }