diff options
author | Florrie <towerofnix@gmail.com> | 2018-06-05 19:53:57 -0300 |
---|---|---|
committer | Florrie <towerofnix@gmail.com> | 2018-06-05 19:53:57 -0300 |
commit | 8592e25b4d36a83ca1c81d49e24c12e0315085cb (patch) | |
tree | ad1bff1438cbe7f6db624fec581267332e9d3521 |
"Initial" commit
45 files changed, 4052 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b46e92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/node_modules +/**/*.html +/site/static/math-svg +/site/static/media diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9b019b0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 towerofnix + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4fce25 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# [blog][gh-pages] + +A new version of the old blog. Static pages are served now, so that's good. +Check out the blog online [here!][gh-pages] + + [gh-pages]: https://towerofnix.github.io/blog/ diff --git a/build.js b/build.js new file mode 100644 index 0000000..2e096e0 --- /dev/null +++ b/build.js @@ -0,0 +1,751 @@ +const fs = require('fs') +const path = require('path') +const yaml = require('js-yaml') +const marked = require('marked') +const fixWS = require('fix-whitespace') +const mjAPI = require('mathjax-node') +const cheerio = require('cheerio') + +const { promisify } = require('util') + +const ncp = promisify(require('ncp').ncp) +const writeFile = promisify(fs.writeFile) +const readFile = promisify(fs.readFile) +const readDir = promisify(fs.readdir) +const stat = promisify(fs.stat) + +const mkdir = function(dir) { + return promisify(fs.mkdir)(dir) + .catch(error => { + if (error.code === 'EEXIST') { + return + } else { + throw error + } + }) +} + +const SITE_ORIGIN = (process.env.BLOG_ORIGIN || 'http://localhost:8000/') +const AUTHOR = (process.env.BLOG_AUTHOR || 'Florrie') + +mjAPI.config({ + MathJax: {} +}) + +mjAPI.start() + +const mathjaxTypeset = (math, format) => new Promise((resolve, reject) => { + mjAPI.typeset({ + math, format, + svg: true + }, function(data) { + if (data.errors) { + reject(data.errors) + } else { + resolve(data.svg) + } + }) +}) + +async function build() { + console.log('Site origin:', getSiteOrigin()) + + console.log('\x1b[1mSetting up things.\x1b[0m') // ---------------------------------------------- + + process.stdout.write('Setting up directories...') // ............................................ + + await mkdir('site') + await Promise.all([ + mkdir('site/posts'), + mkdir('site/archive'), + mkdir('site/static').then(() => mkdir('site/static/math-svg')) + ]) + + console.log(' Created necessary folders.') + + process.stdout.write('Getting category data...') // ............................................. + + const categoryData = await getCategoryData() + + console.log(` Gotten data for ${Object.keys(categoryData).length} categories.`) + + console.log('\x1b[1mBuilding posts.\x1b[0m') // ------------------------------------------------- + + process.stdout.write('Getting post filenames...') // ............................................ + + const postFiles = (await readDir('site/posts')) + .filter(item => item.endsWith('.md')) + + console.log(` Found ${postFiles.length} markdown files.`) + + console.log('Parsing post texts...') // ......................................................... + + let parsedPostCount = 0 + const posts = await Promise.all(postFiles.map( + f => readFile('site/posts/' + f, 'utf-8').then(parsePostText) + .then(post => { + parsedPostCount++ + process.stdout.write(`\r${Math.floor(parsedPostCount / postFiles.length * 100)}%`) + return Object.assign(post, {sourceFile: f}) + }) + )) + + console.log('\rParsed.') + + /* + process.stdout.write('Filtering just the updated posts...') // .................................. + + const updatedPostFiles = (await Promise.all(postFiles.map(f => + Promise.all([ + stat('site/posts/' + f).then(s => s.mtime), + stat('site/posts/' + path.basename(f, '.md') + '.html').then( + s => s.mtime, + e => 0 + ) + ]).then(([ mtimeSrc, mtimeHTML ]) => { + return mtimeSrc > mtimeHTML ? f : false + }) + ))).filter(Boolean) + + const updatedPosts = posts.filter(p => updatedPostFiles.includes(p.sourceFile)) + + console.log(` Found ${updatedPostFiles.length} .md files that have been modified more recently than their respective .html files.`) + */ + + // For now just rebuild EVERYTHING - only rebuilding updated posts is kind of not good when + // you consider that like categories and "next post in category" and blah blah blah those + // kinds of links and also like uhhhhhhhhhhhhhhhhhhhhhhhh blog origin changes it's all a + // mess and I don't want to figure out how to deal with it without rebuilding everything lul. + const updatedPosts = posts + + process.stdout.write('Writing post pages...') // ................................................ + await Promise.all(updatedPosts.map(post => { + writeFile('site/' + getPostPath(post), generatePostPage(post, categoryData, posts)) + })) + console.log(' Written.') + + process.stdout.write('Writing index (latest post) page...') // .................................. + await writeFile('site/index.html', generatePostPage(getLatestPost(posts), categoryData, posts)) + await writeFile('site/index.md', await readFile('site/posts/' + getLatestPost(posts).sourceFile)) + console.log(' Written.') + + console.log('\x1b[1mWriting archive and category pages.\x1b[0m') // ----------------------------- + + process.stdout.write('Writing archive index page...') // ........................................ + await writeFile('site/archive.html', generateArchiveCategoriesPage(categoryData)) + console.log(' Written.') + + process.stdout.write('Writing every-post archive page...') // ................................... + await writeFile('site/archive/all.html', generateArchivePage(posts)) + console.log(' Written.') + + process.stdout.write('Writing each category\'s own page...') // ................................. + await writeCategoryPages(posts, categoryData) + console.log(' Written.') + + console.log('\x1b[1mWriting other miscellaneous pages.\x1b[0m') // ------------------------------ + // TODO: Don't hard-code these, lol + + process.stdout.write('Writing "About" page...') // .............................................. + await writeFile('site/about.html', generateAboutPage(await readFile('site/about.md', 'utf-8'))) + console.log(' Written.') +} + +const generateStaticPage = (md, head = '') => ( + generateSitePage(head, marked(md)) +) + +const generateAdjacentPostLinks = (post, categoryData, allPosts) => { + const postDate = getDate(post) + + const dateReduce = fn => (choice, cur) => { + const curDate = getDate(cur) + const choiceDate = choice ? getDate(choice) : 0 + if (fn(curDate, choiceDate)) { + return cur + } else { + return choice + } + } + + const reduceNextByDate = dateReduce( + (curDate, choiceDate) => curDate > postDate && (!choiceDate || curDate < choiceDate) + ) + + const reducePreviousByDate = dateReduce( + (curDate, choiceDate) => curDate < postDate && (!choiceDate || curDate > choiceDate) + ) + + const nextByDate = allPosts.reduce(reduceNextByDate, null) + const previousByDate = allPosts.reduce(reducePreviousByDate, null) + + const filterPostsWithSameCategory = cat => p => { + return (p.config.categories || []).includes(cat) + } + + const nextByCategories = (post.config.categories || []).map(cat => { + return [cat, + allPosts + .filter(filterPostsWithSameCategory(cat)) + .reduce(reduceNextByDate, null) + ] + }).filter(([ cat, post ]) => !!post) + .filter(([ cat, post ]) => post !== nextByDate) + + const previousByCategories = (post.config.categories || []).map(cat => { + return [cat, + allPosts + .filter(filterPostsWithSameCategory(cat)) + .reduce(reducePreviousByDate, null) + ] + }).filter(([ cat, post ]) => !!post) + .filter(([ cat, post ]) => post !== previousByDate) + + const makeAdjacentInCategoriesLinks = postsByCategories => { + if (postsByCategories.length) { + return fixWS` + (by date; in ${postsByCategories + .map(([ cat, post ]) => fixWS` + <a href='${getPostPermalink(post)}'>${categoryData[cat].title}</a> + `) + .join(', ') + }) + ` + } else { + return '' + } + } + + // TODO: fix-whitespace hates this string, for some reason?? + return fixWS` + ${previousByDate + ? fixWS` + <a href='${getPostPermalink(previousByDate)}'>Previous post</a> + ${makeAdjacentInCategoriesLinks(previousByCategories)} + ` + : '' + } + ${nextByDate ? '<br>' : ''} + ${nextByDate + ? fixWS` + <a href='${getPostPermalink(nextByDate)}'>Next post</a> + ${makeAdjacentInCategoriesLinks(nextByCategories)} + ` + : '' + } + ` +} + +const generatePostPage = (post, categoryData, allPosts) => { + const categories = post.config.categories + + const categoryLinks = ( + (categories && categories.length > 0) + ? categories.map(id => fixWS` + <a href='${getCategoryPath(id)}'>${categoryData[id].title}</a> + `) + : null + ) + + const categoryLinkText = ( + categoryLinks + ? 'categories: ' + categoryLinks.join(', ') + : 'no categories' + ) + + return generateSitePage( + // No description-meta here.. yet. In theory there should be, but Google + // does a pretty good job at guessing descriptions from the content of + // the post. + fixWS` + <title>${post.config.title}</title> + ${generateMetaHead({ + 'Description': getOneSentence(getPostDescription(post)), + 'twitter:card': post.config.presentArt ? 'summary_image' : 'summary', + 'twitter:site': '@towerofnix', + 'twitter:title': post.config.title, + 'twitter:description': getPostDescription(post), + 'twitter:image': ( + post.config.thumbnail + ? (getSiteOrigin() + post.config.thumbnail) + : null + ), + 'twitter:image:alt': null // </3 TODO: fix this? + })} + `, + + fixWS` + ${post.html} + <p class='post-meta'> + (-${AUTHOR}, + <a href='${getPostPermalink(post)}'>${getTimeElement(post)}</a> + (<a href='${getPostPermalink(post, 'md')}'>markdown</a>); + ${categoryLinkText}) + </p> + <p class='post-meta'> + ${generateAdjacentPostLinks(post, categoryData, allPosts)} + </p> + `, + + fixWS` + <div class='post-nav'> + ${generateAdjacentPostLinks(post, categoryData, allPosts)} + </div> + ` + ) +} + +const generateAboutPage = md => { + const description = ( + "The (slightly) extended description of towerofnix's blog." + ) + + return generateStaticPage( + md, + fixWS` + <title>Blog</title> + ${generateMetaHead({ + 'Description': description, + 'twitter:card': 'summary', + 'twitter:site': '@towerofnix', + 'twitter:title': "About", + 'twitter:description': description, + })} + ` + ) +} + +const generateSitePage = (head, body, extraNav = '') => { + if (!head.includes('twitter')) { + // TODO: It would be really nice to be able to log the title of the page + // here! - But virtually impossible without being smart and parsing the + // head text. + console.warn('No twitter meta tags..?\n' + head + '\n\n') + } + + if (!head.includes('Description')) { + console.warn('No Description meta tag..?\n' + head + '\n\n') + } + + return fixWS` + <!DOCTYPE html> + <html> + <head> + <base href='${getSiteOrigin()}'> + <meta charset='utf-8'> + ${head} + + <link rel='stylesheet' href='static/site.css'> + </head> + <body> + <div id='main' class='top-block'> + <div id='nav'> + <a href='index.html'>(Front.)</a> + <a href='about.html'>(About!)</a> + <a href='archive.html'>(Archive.)</a>${ + // TODO: fix-whitespace doesn't like whitespace-only lines?? + extraNav + } + </div> + <div id='content'> + ${body} + </div> + </div> + <footer class='top-block'> + <p>My posts and art are licensed under + <a href='https://creativecommons.org/licenses/by-sa/4.0/'> + CC BY-SA 4.0</a>, and this blog's source code is available + <a href='https://github.com/towerofnix/blog'>online</a>!</p> + </footer> + </body> + </html> + ` +} + +const generateMetaHead = dict => { + return Object.entries(dict) + .filter(([ k, v ]) => { + if (!v) { + // It's normal to have a null value (that means "there's nothing in + // this field"), but for there to be an undefined value (or + // otherwise)..? That's strange! + if (v !== null) { + console.warn('Unset ' + k + '?', dict) + } + + return false + } + + return true + }) + + // fix-whitespace is broken??????? yikes.jpg + // .map(([ k, v ]) => fixWS` + // <meta name='${k}' content="${ + // v.replace(/"/g, '"').replace(/\n/g, '__-\n-__') + // }"> + // `) + + .map(([ k, v ]) => ( + `<meta name='${k}' content="${v.replace(/"/g, '"')}">` + )) + .join('\n') +} + +const generateArchivePage = posts => ( + generateArchiveCategoryPage( + '', // just use the default title, Archive (not "Archive - Archive"!) + fixWS` + A quick (read: long) table of all of the posts I've published here. + See also the <a href='archive.html'>category-based archive</a>. + `, + posts + ) +) + +const generateArchiveCategoriesPage = (categoryData) => { + const description = ( + "A list of categories the posts on the blog are organized into." + ) + + return generateSitePage( + fixWS` + <title>Archive</title> + ${generateMetaHead({ + 'Description': description, + 'twitter:card': 'summary', + 'twitter:site': '@towerofnix', + 'twitter:title': 'Archive', + 'twitter:description': description + })} + `, + + fixWS` + <h1>Archive</h1> + <p>A list of the categories the posts on the blog are organized + into. See also the <a href='archive/all.html'>archive of all + posts</a>.</p> + + ${generateCategoryList(categoryData)} + ` + ) +} + +const writeCategoryPages = (posts, categoryData) => ( + Object.entries(categoryData).map( + ([id, cat]) => writeFile( + 'site/' + getCategoryPath(id), + generateArchiveCategoryPage( + cat.title, cat.description, + posts.filter(post => post.config.categories.includes(id)) + ) + ) + ) +) + +const generateArchiveCategoryPage = (title, description, posts) => { + const fullTitle = 'Archive' + (title ? ` - ${title}` : '') + + return generateSitePage( + fixWS` + <title>${fullTitle}</title> + ${generateMetaHead({ + 'Description': getOneSentence(getHTMLDescription(description)), + 'twitter:card': 'summary', + 'twitter:site': '@towerofnix', + 'twitter:title': fullTitle, + 'twitter:description': getHTMLDescription(description) + })} + `, + + fixWS` + <h1>${title || 'Archive'}</h1> + <p>${description}</p> + + ${generateArchiveTable(posts.sort((a, b) => getDate(a) - getDate(b)))} + ` + ) +} + +const generateArchiveTable = posts => ( + fixWS` + <table> + <colgroup> + <col> + <col class="date-col"> + </colgroup> + <tbody> + ${ + posts.map( + post => { + const link = getPostPath(post) + + return fixWS` + <tr> + <td><a href='${link}'>${post.config.title}</a></td> + <td>${getTimeElement(post)}</td> + </tr> + ` + } + ).join('\n') + } + </tbody> + </table> + ` +) + +const generateCategoryList = (categoryData) => ( + fixWS` + <ul> + ${ + Object.entries(categoryData).map( + ([id, category]) => fixWS` + <li> + <a href='archive/${id}.html'>${category.title}:</a> + ${category.description} + </li> + ` + ).join('\n') + } + </ul> + ` +) + +const parsePostText = async (text) => { + // Parses the text contents of a post .md file. + // + // YAML configuration is stored before a '---' separator. Markdown content + // comes after the separator. + // + // It's recommended to indent the configuration code before '---' as a code + // block, so that it's valid Markdown code. + + const separator = '\n---\n' + + const separatorIndex = text.indexOf(separator) + const code = text.slice(0, separatorIndex) + const markdown = text.slice(separatorIndex + separator.length).trim() + + let config + + try { + config = yaml.safeLoad(code) + } catch (err) { + throw new Error('Invalid YAML: ' + err.message) + } + + // We SHOULD use a custom Marked renderer, but MathJax runs asynchronously, + // while marked expects a synchronous value. To be fair you could hack around + // that (e.g. by storing a key to each math element, and then rendering those + // after running marked), but that's a little yucky, and we can get inline + // math if we deal with MathJax and Marked separately. + + let processedMarkdown = markdown + + let processedMath = [] + + const handleMatch = (texType, applyTemplateFunc) => match => { + const reference = match[1] + const mathText = match[2] + + const directoryName = `static/math-svg/${config.permalink}` + const svgFile = `${directoryName}/${reference}.svg` + + processedMath.push({reference, mathText}) + + return mkdir('site/' + directoryName) + .catch(err => { + if (err.code !== 'EEXIST') { + throw err + } + }) + .then(() => mathjaxTypeset(mathText, texType)) + // TODO: Sanitize this.. I'm going to inevitably write a slash in a + // math ID, at some point! + .then(math => writeFile('site/' + svgFile, math)) + .then(math => applyTemplateFunc(reference, svgFile)) + } + + processedMarkdown = await processMarkdown(processedMarkdown, + /<pre class='math' id='([^']*)'>([\s\S]+?)<\/pre>/g, + handleMatch('TeX', (reference, svgFile) => fixWS` + <p class='math' id='${reference}'> + <img src='${svgFile}'> + </p> + `) + ) + + processedMarkdown = await processMarkdown(processedMarkdown, + /<code class='math' id='([^']*)'>([\s\S]+?)<\/code>/g, + + handleMatch('inline-TeX', (reference, svgFile) => fixWS` + <span class='inline-math' id='${reference}'><img src='${svgFile}'></span> + `) + ) + + processedMarkdown = await processMarkdown(processedMarkdown, + /<art>([^<]*)<\/art>/g, + match => fixWS` + [![${match[1].match(/^[0-9]+-(.*)$/)[1]}](static/media/${encodeURIComponent(match[1])}.png)](static/media/${encodeURIComponent(match[1])}.png) + ([${match[1].match(/^[0-9]+-(.*)$/)[1]}.kra](static/media/${encodeURIComponent(match[1])}.kra)) + ` + ) + + return { + config: yaml.safeLoad(code), + html: marked(processedMarkdown), + markdown: markdown // Explicit is usually better than implicit + // (The meaning of the above comment is implied) + } +} + +const getPostPath = (post, extension = 'html') => ( + `posts/${post.config.permalink}.${extension}` +) + +const getPostPermalink = (post, extension = undefined) => ( + getSiteOrigin() + getPostPath(post, extension) +) + +const getPostDescription = post => { + if (post.config.description) { + return post.config.description + } + + return getHTMLDescription(post.html) +} + +const getHTMLDescription = html => { + const $ = cheerio.load(html) + + // Some paragraphs can only include images; we don't want to use those for + // getting the description text. + for (let p of $('p').get()) { + const text = $(p).text() + + if (text) { + return text + } + } + + return $.root().text() +} + +const getOneSentence = text => { + let i = 0 + + while (i < text.length && text[i - 1] !== '.') { + i++ + } + + const textRange = text.slice(0, i) + const sentence = textRange.replace(/\n/g, ' ') + + return sentence +} + +const getCategoryPath = id => ( + `archive/${id}.html` +) + +const getCategoryData = () => ( + readFile('categories.yaml', 'utf-8') + .then(text => yaml.safeLoad(text)) + .then( + yamlObj => { + const ret = {} + + for (let entry of yamlObj.categories) { + ret[entry.id] = entry + } + + return ret + }, + err => { + throw new Error("YAML Error: " + err.message, err) + } + ) +) + +const getLatestPost = posts => { + let latestPost = posts[0] + let latestDate = 0 + + for (let curPost of posts) { + const curDate = getDate(curPost) + if (curDate > latestDate) { + latestPost = curPost + latestDate = curDate + } + } + + return latestPost +} + +const getDate = post => { + if (!('date' in post.config)) { + console.warn(`Post ${post.config.title} has no date!`) + return null + } + + const { y, m, d } = post.config.date + + return new Date(y, m - 1, d) +} + +const getTimeElement = post => { + const date = getDate(post) + + const datetime = !date ? '' : asDatetime(date) + + const humanDatetime = ( + !date ? '(No date)' : asHumanReadableDate(date) + ) + + return `<time datetime='${datetime}'>${humanDatetime}</time>` +} + +const asDatetime = date => ( + `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` +) + +const asHumanReadableDate = date => { + const months = [ + 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', + 'September', 'October', 'November', 'December' + ] + + const monthName = months[date.getMonth()] + + return `${monthName} ${date.getDate()}, ${date.getFullYear()}` +} + +const getSiteOrigin = () => SITE_ORIGIN + +const processMarkdown = async (markdown, regex, matchFunction) => { + let processedMarkdown = '' + let lastIndex = 0 + + while (true) { + const match = regex.exec(markdown) + + if (match) { + processedMarkdown += markdown.slice(lastIndex, match.index) + processedMarkdown += await matchFunction(match) + lastIndex = regex.lastIndex + } else { + break + } + } + + processedMarkdown += markdown.slice(lastIndex) + + return processedMarkdown +} + +if (require.main === module) { + build() + .catch(error => console.error( + `${error.name} - ${error.reason}\n${error.stack}` + )) +} diff --git a/categories.yaml b/categories.yaml new file mode 100644 index 0000000..98fb7cf --- /dev/null +++ b/categories.yaml @@ -0,0 +1,13 @@ +categories: + +- id: 'art' + title: "Art" + description: "\"Artistic\" creations (probably things I've made)." + +- id: 'dev' + title: "Development" + description: "Posts related to programming things." + +- id: 'text' + title: "Text-based Posts" + description: "Particularly wordy posts." diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fd602af --- /dev/null +++ b/package-lock.json @@ -0,0 +1,840 @@ +{ + "name": "blog", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.0.tgz", + "integrity": "sha512-h3YZbOq2+ZoDFI1z8Zx0Ck/xRWkOESVaLdgLdd/c25mMQ1Y2CAkILu9ny5A15S5f32gGcQdaUIZ2jzYr8D7IFg==" + }, + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=" + }, + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "4.0.13" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash": "4.17.5", + "parse5": "3.0.3" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "content-type-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", + "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" + }, + "cssom": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=" + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "requires": { + "cssom": "0.3.2" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "escodegen": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", + "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "requires": { + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fix-whitespace": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fix-whitespace/-/fix-whitespace-1.0.3.tgz", + "integrity": "sha1-eTVKgWzBKguT+HTkxeA8S5mR9pU=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "1.0.3" + } + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsdom": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", + "integrity": "sha1-6MVG//ywbADUgzyoRBD+1/igl9Q=", + "requires": { + "abab": "1.0.4", + "acorn": "4.0.13", + "acorn-globals": "3.1.0", + "array-equal": "1.0.0", + "content-type-parser": "1.0.2", + "cssom": "0.3.2", + "cssstyle": "0.2.37", + "escodegen": "1.9.1", + "html-encoding-sniffer": "1.0.2", + "nwmatcher": "1.4.4", + "parse5": "1.5.1", + "request": "2.85.0", + "sax": "1.2.4", + "symbol-tree": "3.2.2", + "tough-cookie": "2.3.4", + "webidl-conversions": "4.0.2", + "whatwg-encoding": "1.0.3", + "whatwg-url": "4.8.0", + "xml-name-validator": "2.0.1" + }, + "dependencies": { + "parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=" + } + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "katex": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.7.1.tgz", + "integrity": "sha1-BrtSmO+tBeHnIoA1uo4VkfMGG48=", + "requires": { + "match-at": "0.1.1" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "marked": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.18.tgz", + "integrity": "sha512-49i2QYhfULqaXzNZpxC808PisuCTGT2fgG0zrzdCI9N3rIfAWfW0nggvbXr6zvpynZdOG5+9xNxdzP0kwZnERw==" + }, + "match-at": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz", + "integrity": "sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==" + }, + "mathjax": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.3.tgz", + "integrity": "sha512-HEKJYRofOD5k9RmTj1amdA32JIJOqJ8SO+lq50mffZzdfFz+bNnsB0tJ8efMfYpiffvUHkqFRZFxXftW4BaBOw==" + }, + "mathjax-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mathjax-node/-/mathjax-node-1.3.0.tgz", + "integrity": "sha1-pYyQoYXZCbYyOxnA2xUB0UrABww=", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "jsdom": "9.12.0", + "mathjax": "2.7.3" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "1.0.0" + } + }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "9.6.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "1.4.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "whatwg-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", + "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", + "requires": { + "iconv-lite": "0.4.19" + } + }, + "whatwg-url": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", + "integrity": "sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA=", + "requires": { + "tr46": "0.0.3", + "webidl-conversions": "3.0.1" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + } + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d5c4be --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "dependencies": { + "cheerio": "^1.0.0-rc.1", + "fix-whitespace": "^1.0.2", + "js-yaml": "^3.8.3", + "katex": "^0.7.1", + "marked": "^0.3.6", + "mathjax-node": "^1.1.0", + "ncp": "^2.0.0" + }, + "name": "blog", + "description": "A new version of the old blog. Static pages are served now, so that's good. Check out the blog online here! https://towerofnix.github.io/blog/", + "version": "1.0.0", + "main": "build.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "node build.js", + "publish": "export BLOG_ORIGIN='https://towerofnix.github.io/blog/' && node build.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/towerofnix/blog.git" + }, + "keywords": [ + "gh-pages", + "blogs", + "blog-engine" + ], + "author": "towerofnix", + "license": "MIT", + "bugs": { + "url": "https://github.com/towerofnix/blog/issues" + }, + "homepage": "https://github.com/towerofnix/blog#readme" +} diff --git a/publish.sh b/publish.sh new file mode 100755 index 0000000..7b25b8e --- /dev/null +++ b/publish.sh @@ -0,0 +1,5 @@ +export BLOG_ORIGIN='https://florrie.ed1.club/' +node build.js + +echo '-- Publish:' +echo '-- rsync -a site/ florrie@ed1.club:/home/florrie/public_html' diff --git a/site/about.md b/site/about.md new file mode 100644 index 0000000..a7155db --- /dev/null +++ b/site/about.md @@ -0,0 +1,39 @@ +# towerofnix's Blog Site + +Welcome to the site! I use this place to [ramble](archive/text.html) and +[share stuff I've drawn](archive/art.html). Maybe some of it'll be interesting +to someone. + +## FAQ + +**I want to contact you or comment on one of your posts but have no idea how.** +Sending me a tweet [@towerofnix][twitter], a post on [mastodon][masto], or +[an email][guess] is probably the best way right now. + +**towerofnix is really hard to pronounce and write and say. Any other names??** +You might want to stick to towerofnix if you're specifically talking about, +e.g., my blog, my YouTube channel, or whatever. That's the username I've taken +for myself, and it's what people can search to find me. But if you're talking +about me, as a person, please use Florrie. (If you're curious, there's a whole +blog post on this topic [here][usernames-post]!) + +**What's this blog engine?** +It's a completely custom, hand-made one. [Here's the source code][blog-source] +if you want to use it or just play around with it. + +**What the eff is the standard text editor and why is it ed(1)?** +Apparently, ed is useful because you can use it on just about any Unix system. +(I think that's why the call it "standard".) So if you know how to use it, +you'll never be without the ability to harness its amazing splendid powers. +A little wikipediabird is telling me that ed was developed in 1969, which +should be enough reason for *anybody* to learn it, but I've never actually +used it myself. Oops. + +**How frequently asked are these questions?** Never. Ask me some questions, +please? (Hint: see FAQ.) + + [twitter]: https://twitter.com/towerofnix + [masto]: https://cybre.space/@florrie + [guess]: https://en.wiktionary.org/wiki/guess#TRY-GMAIL + [usernames-post]: posts/20-on-confusion-related-to-my-usernames.html + [blog-source]: https://github.com/towerofnix/blog diff --git a/site/index.md b/site/index.md new file mode 100644 index 0000000..a4901e7 --- /dev/null +++ b/site/index.md @@ -0,0 +1,38 @@ + + title: "Art dump May!!!!!" + permalink: '35-art-dump-may' + date: {m: 6, d: 1, y: 2018} + categories: + - 'art' + +--- + +# Art dump May!!!!! + +Maaay!!!!!!!!!! Whee. A few new things! + +<art>35-Sleeeeeeep</art> + +<art>35-nocrashpls</art> + +<art>35-choc</art> + +<art>35-dangit</art> + +<art>35-Fiiiire</art> + +<art>35-gh</art> + +<art>35-pixeljunkeqen</art> + +This one just above is based on [a thing eq made!](https://eq.ed1.club/art/2018-05-06-.html) + +<art>35-spin</art> + +<art>35-creepyabstract</art> + +<art>35-brushtest</art> + +<art>35-dlothes</art> + +<art>35-drlod</art> diff --git a/site/posts/1-first-post.md b/site/posts/1-first-post.md new file mode 100644 index 0000000..922e11b --- /dev/null +++ b/site/posts/1-first-post.md @@ -0,0 +1,54 @@ + + title: "First Post" + permalink: '1-first-post' + date: {m: 4, d: 10, y: 2017} + thumbnail: 'static/media/00-puppet-thing.png' + presentArt: true + categories: + - 'art' + - 'text' + +--- + +# First Post + +Hello, world! Or whatever. + +This is just a test blog for now. I'm planning on throwing art and stuff onto +the site, so I guess you can expect that. + +This isn't the first time I've done a blog, and it'll probably be relatively +short-lived like the others, but then again, I actually programmed this from +scratch, and it's a kind of interesting setup (serverless/single-page). Here's +the [GitHub repository][repo] if you're curious! + +--- + +I guess I'll explain. + +In the very beginning(tm), I used Scratch to post art. Haha. Scratch was +flawed, though. I wrote a little bit (somewhat indirectly) about that +[here][scratch-flaws]. But really Scratch just wasn't designed to, well, +"professionally" share art. + +So for a while I was using Twitter to post art, instead. This wasn't perfect, +either, though. Twitter is nice for sharing random junk like +[this][neck-sizes], but not so much art projects I've put semi-significant +amounts of work into. It's not a very good place for sharing detailed feedback. + +What I eventually noticed is that a blog would work best. Just, you know, a +place where I can post updates on the stuff I've made, and at least have the +technical potential for better discussion. So I programmed a blog, because I'm +too lazy to mess around with server hosts and Jekyll and other horrible stuff. +(And also because I wanted to. Obligatory [motivation.][motivation]) + +--- + +Here's a picture, for good measure's sake. + +![Puppet thing](static/media/00-puppet-thing.png) + + [repo]: https://github.com/towerofnix/blog + [scratch-flaws]: https://scratch.mit.edu/projects/152633913/ + [neck-sizes]: https://twitter.com/towerofnix/status/844198786410930176 + [motivation]: https://youtu.be/uNjxe8ShM-8?t=1m41s diff --git a/site/posts/10-stuff-filled-update.md b/site/posts/10-stuff-filled-update.md new file mode 100644 index 0000000..58bc223 --- /dev/null +++ b/site/posts/10-stuff-filled-update.md @@ -0,0 +1,77 @@ + + title: "Stuff-filled update" + permalink: '10-stuff-filled-update' + date: {m: 5, d: 20, y: 2017} + categories: + - 'text' + - 'dev' + +--- + +# Stuff-filled update + +This last week or so was pretty productive! I think the world ended a few +times, the general state of everything everywhere got worse, and several of +the things you always thought were good have apparently been for evil and +malevolent causes all this time. Welcome to humanity. (Some exaggeration +implied.) + +*Locally,* I made a few things! I'd originally written this in chronological +order but that didn't work well. No sorting guaranteed from here on. + +I made a Dragon Quest IX alchemy thing. You give it the name of an alchemy +recipe; it tells you all the ingredients that are needed, and all the +ingredients required to make each of those ingredients, and so on (down to +ingredients that can't be alchemized). That took a while to make - *mostly* +because of the gigantic file storing all the recipes in the game (that I've +discovered) that I typed out by hand. It mostly looks like +[this](static/media/10-recipe-file-screenshot.png). + +I worked on Ludum Depot 38 Cool Edition a bit. [Ludum Depot 38][ld38] is a +game [eevee][eevee] and [glitchedpuppet][glip] made; Cool Edition is an +endless (infinity waves) version of that game that I'm working on. Basically +I implemented a few suggestions that Opa-Opa gave; +[read them](static/media/10-thanks.png) if you're interested. + +I made a program that more or less imports images into [Sploder][sploder] +games. (You can't import images in the custom graphics editor, and even if you +could, they'd only be 16x16 tiles; which wouldn't be manually feasible for +large tiles!) That ended up looking something like this: + +![Sploder magic.](static/media/10-sploder-magic.png) + +I wrote a bit about it [here][sploder-magic]. + +I made [a quick "game"][cupcakes]. It's pretty boring; the main fun of it was +implementing [the engine behind it][cupcake-src] from scratch. (The only really +useful part was seeing klinklang's object implementation, which was quite +possibly stolen from somewhere else. I'd link you to klinklang, but it's a +library that's somehow been split up and used in a bunch of eevee's games; +it doesn't have any central repository as far as I know, so I'll just direct +you to [eevee's GitHub page](https://github.com/eevee/).) + +I turned [a whole bunch of GameFAQs text][grotto-guide] into a more +human-readable PDF and [printed it][printed-guide]. I'm pretty satisfied with +the result, even though I'll probably never read that guide more than once or +twice (and then, only specific parts of it); but then again, that's kind of +how references are. + +I also technically drew a [thing](static/media/10-whelp.png), but I'm not +really impressed by it. I didn't *really* feel like drawing when I made that +\- you know, that itch to make something and see how it turns out (even if it +ends up entirely imperfectly/terribly). So it isn't quite as close to me as +some of the other stuff I've drawn (mostly what I've posted on this blog). + +So not a horribly boring week! Better than [quick update][a-quick-update]'s +one (er, three), that's for sure. + + [ld38]: https://eevee.itch.io/lunar-depot-38 + [eevee]: https://twitter.com/eevee/ + [glip]: https://twitter.com/glitchedpuppet/ + [sploder]: http://sploder.com/ + [sploder-magic]: http://forums.sploder.com/index.php/topic,504355.msg6410307.html + [cupcakes]: https://twitter.com/towerofnix/status/864434231107366912 + [cupcake-src]: https://github.com/towerofnix/Lexy-and-the-Quest-for-Cupcakes/ + [grotto-guide]: https://www.gamefaqs.com/ds/937281-dragon-quest-ix-sentinels-of-the-starry-skies/faqs/61151 + [printed-guide]: https://twitter.com/towerofnix/status/865376968249925633 + [a-quick-update]: posts/7-quick-update.html diff --git a/site/posts/11-raspberry-pi-madness.md b/site/posts/11-raspberry-pi-madness.md new file mode 100644 index 0000000..098d3fd --- /dev/null +++ b/site/posts/11-raspberry-pi-madness.md @@ -0,0 +1,163 @@ + + title: "Raspberry Pi madness" + permalink: '11-raspberry-pi-madness' + date: {m: 5, d: 26, y: 2017} + thumbnail: 'static/media/11-setup.png' + presentArt: true # ..this should probably be presentImage.. + categories: + - 'text' + - 'dev' + +--- + +# Raspberry Pi madness + +I got a Raspberry Pi, so, here's some rambling about it and what I've made +with it. + +First things first - the Raspberry Pi is *weird.* It's just funny having a tiny +little computer that can do pretty much anything (that isn't terribly +computationally powerful). I guess it's not too surprising, since mobile +devices are already way more powerful (and nearly smaller) than the Raspberry +Pi; but this thing feels a bit different, maybe because I'm actually able to +program it..? (But then there's Pythonista, too. Maybe it's the ability to +SSH into the box sitting on my desk?) + +It's also just fun to look at. I took a picture of it: + +![Setup](static/media/11-setup.png) + +(That picture looks an awful lot like an album cover.) + +Although my new setup is a bit less crazy (I got rid of the HDMI output, +keyboard and mouse). + +There's one distinct thing in that photo, though - the radio. I've got three +reasons for using that radio, instead of the HDMI TV output: + +**Reason one:** I don't want it to rely on HDMI, since sometimes HDMI might +not be available. + +**Reason two:** Radios give a nicer feeling. Having the radio speaker output +sound seems more friendly than a gigantic TV (and/or a dedicated monitor) +doing it. + +**Reason three:** I wasn't using the radio anyways. + +Okay, but, *what am I doing with it?* + +Short answer: it's playing my iTunes library. + +Long answer: it's downloading song files I have in my iTunes media folder and +playing those. No files are stored on the Raspberry Pi (except for the +currently playing track). I also made it be able to filter things by "group", +which really just means by artist and (optionally) album. + +..Ah, who am I kidding? This intro's way too long. I just want to show how it +actually works! + +--- + +## Generating the Playlist + +The first thing we need to do is create a file containing all the songs and the +URLs we can use to download them. I like using `python3 -m http.server` as my +main static server, because it only takes that one command to use, so we'll try +to parse the output of that. + +Thankfully, the format for directory listings with that server is relatively +simple; every `<a>` on the page links to a file or a deeper folder. Since we're +building a *tree* of the entire library (root -> artists -> albums), we'll need +to navigate the directory recursively; to do that we just run the crawl-folder +function on every `<a>` href-path that ends with `/`. + +Like I noted, the tree we end up with goes from the root folder, to a folder +for each artist, and then a folder for each of an artist's albums. + +There's actually a setting in the iTunes preferences that does all of that +sorting for us - under Advanced Preferences, "Keep iTunes Media folder +organized". That's enabled by default, I think. How convenient. + +I wrote the program that crawls the `python3` server in a separate file from +the main `play.js` script; `crawl-itunes.js`. In fact, it doesn't *require* +iTunes in any way; really it'll generate a tree for any `python3` (or similar) +server. (It probably won't work if the server gives parent-directory links, or +if there's links; it isn't built to deal with potential infinite recursion, +since that's not a problem in my case.) + +## Picking the Track + +Our playlist file essentially acts as a big listing of track titles and links +to the HTTP download for respective files; we'll use it to pick our song. +(That's what it's made for, after all!) + +The thing is, we want to randomly pick a track from all the tracks in our +playlist file. But our playlist file is a tree. We have two options here: + +* Recursively pick a random file or sub-group, until we hit a file. Use that. + +* Flatten the tree, then pick a random item. Use that. + +I went with the second option. The actual code behind it is boring, obviously, +so I won't bother going over it; but in summary we really are just flattening +the tree and picking a random item. + +Also, now's a good time to mention the optional "group path" option I made for +the program. If it's specified, the track-selector will only pick from tracks +under the passed group. (Sub-groups are split by `/`; so, for example, you +could make it only pick tracks from *Epic Battle Fantasy IV* by giving it the +group path string `'Phyrnna/Epic Battle Fantasy IV'`.) + +## Downloading the Music + +Before we can actually play anything, we'll need to download the track we want. +Just downloading the entire track before playing it works; the Raspberry Pi and +the device with the iTunes library are on the same network, so there's no +significant buffering. + +Our picked track playlist-item conveniently stores two pieces of information: +the title of the song[\*] and the download link for it. Clearly we'll need the +download link. It's just the HTTP URL we can fetch to download the track; so +that's what we do. + +[\*] Really the text between `<a href="..">` and `</a>`; not the actual +title of the track that's stored in the file's metadata. I'm too lazy to +figure out how to get that, plus I think some formats don't even have any +metadata of that sort. + +## Converting the Audio File + +Before we can actually play the file, we still need to do one thing. +Realistically, our files aren't going to all be in a format the play command +we're using will accept; we need to convert them to a standard format, like +WAV, first. + +That's not actually difficult, though. We can use `avconv`[\*] to convert the +downloaded file using a child process; that's just `avconv -y -i in out.wav`. +(`-y` is there to ignore any "should this file be overwritten?" prompts.) + +[\*] Originally I'd thought to use `ffmpeg`, but apparently that doesn't +actually exist as a proper package, and `avconv` is the more standard fork +people use. But it looks like `ffmpeg` and `avconv` work basically the same +way, at least to the user; so at least I didn't also have to learn a whole new +command-line program! + +## Playing the Audio File + +Playing the audio file is as easy as running the `play` command. In fact, +that's what we do! + +This step is actually that stupidly simple. It's literally just `play out.wav`. + +And then we loop back to picking another track! + +## What else can this do? + +I'd like to make a playlist file that somehow plays music from a YouTube +channel that has tons of music uploads; SiIvaGunner and HeavyMetal Rocker1988 +come to mind. That shouldn't be too hard to do, but it would probably mean +either making an in-between server that downloads and responds with the +downloaded YouTube audio (*stupid!*) or making the program work by downloading +YouTube audio, directly onto the Raspberry Pi (not stupid). + +Making it into an actual radio transmitter would be cool, too. diff --git a/site/posts/12-nondaily-update.md b/site/posts/12-nondaily-update.md new file mode 100644 index 0000000..42f461e --- /dev/null +++ b/site/posts/12-nondaily-update.md @@ -0,0 +1,202 @@ + + title: 'Non-daily Update' + permalink: '12-nondaily-update' + date: {m: 5, d: 31, y: 2017} + categories: + - 'text' + - 'dev' + +--- + +# Non-daily Update + +I made myself a text file for storing a log of the things I do in a few days +ago. It's kind of shocking how huge it is; I really feel more productive when +I can write a paragraph or two about all the different things I do in a day. +I might release the whole file somewhere, at some point; meanwhile here's a +bunch of excerpts from my log so far (it's ~2000 words long in just four days, +haha). + +(This really is just an excerpt, though; I cut out a fair amount of videos or +school that I didn't have much to comment on. Think of this page as a 100% +quote of the interesting things in my log.) + +(I added a bunch of links for context; all the content was written by me, +generally not long after I did whatever I mentioned in each paragraph.) + +## May 28th + +* I did a quiz on the general topic of waves in physical science. I started + writing proper notes (as I do for my geometry); they’re helpful, but it’s + a bit annoying that I didn’t start earlier. Oh well. + +* I merged Broadcast Vars into [Scrap][scrap]. Scrap is pretty much finished + at this point. + +* I looked up [Baiyon][baiyon] after a [PixelJunk Eden track][pj-eden] + randomly played on iTunes. Apparently he collaborated with [C418][C418] to + make the track [185][185]! What a small world. + +* I discovered Eden Obscura, a new PixelJunk game. This is being made RIGHT + NOW, so it'll be something to keep an eye out for! + +* I learned not to use the `fs-promise` NPM module anymore, since it's + deprecated. Opt for `mz/fs` instead! + +* I read [a blog post][introspection] by [eevee][eevee]; it's quite + ridiculously relatable (..though I haven't tried to do all the things I want + to, yet..) and worth reading in general. + +* I read eevee's [weekly update][weekly-update]. "I'm feeling pretty good?" is + generally the way I'd describe the days that go well for me, too. — This was + a comforting little paragraph to read: + + > I was held at gunpoint by glip and forced to help them design my own + > characters, which is, extremely cool and good. I’m starting to think they + > like doing creative work with me and think I have interesting ideas??" + +## May 29th + +* I read a few old stories I made and published on a blog I don't use anymore. + They weren't that bad, considering I was around five when I wrote them. + +* I did a section on electromagnetic waves (and the electromagnetic spectrum) + in my physical science course. I don't feel like the course teaches it very + well; they never seem to give explanations on WHY anything works the way it + does. I guess since this is essentially an introductory course, I'm not + really supposed to expect much of that, but.. it'd be more interesting if + they explained why things work the way they do at least a little, you know? + + While doing that I also wrote a few paragraphs on the math behind some + behaviour of waves; I think that it'll be helpful, considering I've been + having a bit of trouble totally understanding the actual logic behind how + waves work. + + Also, the questions for my science always feel a bit.. stupid? They're + pretty much always a matter of "can you remember all the facts we threw at + you in the lecture?" or "did you take enough notes? - did you catch that + one detail?". Of the five questions on the section, three of them were just + "which is true?" questions on the positioning of different types of waves + on the EM spectrum. + + (I mentioned that I'm not really satisfied with what they teach, right? + Like, they didn't even explain what the difference between electromagnetic + and non-electromagnetic waves are.. just that some waves fall on the EM + spectrum, and some don't, and that the ones that do are EM waves.) + +* I watched [a video][smw] on a jailbreak for Super Mario World. It's a bit + crazy to think that people are still discovering incredible bugs and tricks + for ancient games like Super Mario World. + +* I worked on my Raspberry Pi music player a bit. That included improving + the reliability of the iTunes/`python3 -m http.server` crawler; there + shouldn't be any more missing albums or artists. I also added a few options + to the command line interface for it; you can now keep and ignore specific + groups. It's not technically more powerful than iTunes itself, but I'd say + it's a lot more comparable, now, and potentially easier to use. + +* I watched [time mop][time-mop] by [bill wurtz][billwurtz]. I wonder what + makes the part where it goes past zero so funny? Maybe it's just the fact + that it's doing something so *obviously* intuitively right ("it's already + going down, why stop at zero?") but also entirely unexpected (countdowns + usually do stop at zero), combined with the happy, fun music in the + background. + +## May 30th + +* I did a bunch of geometry and learned about transformation matrices. Well, + it was mostly the topic of adding and multiplying matrices; that's not too + surprising, though (and fine; it was interesting anyways). I wrote a bunch + on multiplying matrices, but it only works in one case (when multiplying AxB + \* BxA matrices), which doesn't help much when the other case (multiplying + AxB \* BxC matrices) is also valid. I'm pretty sure the lecture skipped + glossed over that case. + +* I worked on the HTTP music player and added a fair few number of new + features. The most important one is that it now preloads tracks while one is + already playing; basically, it does all the work it needs to do to play a + track while one is already playing, so that you don't notice it doing that + work, and there's no significant pause between two tracks anymore. I also + made it possible to play tracks in order rather than shuffled, fixed a bug + or two, and cleaned up a bunch of code. + +* I read a few forum threads on the old Scratch forums (pre-2.0). They seem to + have been a nicer place back then. + +* I worked a bit on my algebra on KhanAcademy and got to a point where I + mastered 500 skills. That's nice. I also 100%-mastered a few earlier grade + levels (third and fourth), as well as arithmetic; though they were all + pretty much already mastered. + +## May 31st + +* I wrote a bit on [an idea][grandia] to make Grandia III have competitive + battles. I don't actually know how, though, and probably nobody on that + subreddit does either, but whatever. The idea's still there. + +* I read an (adapted) article titled "Will Technology Make You + Healthier?". It's relatively self-descriptive, but it did touch on + less obvious subjects like the controversy around future possibilities. + (Is it alright to take from a human embyro to help someone, if in the + process, the embyro will be destroyed?) + +* I watched ["The Speed of Light is NOT About Light"][speed-of-light], by + [PBS Space Time][space-time]. + + It generally explained how the speed of light really doesn't derive from + the speed that light travels at (rather it's the reverse), but I didn't + find that it explained HOW particularly well (essentially they said that + the universal constant, which the speed of light is equivalent to, must + have the value that it does because of various other universal rules; but + they didn't really explain the math that leads to it actually having the + value it does). + + Particular topics I'd like to know more of include how the Lorentz + transformation works, the difference between that and the previously set + system, what Maxwell's equations actually mean, and why they require a + specific constant value for the universal constant. + +* I improved the style for `<code>`s on my blog. I think code looks nicer now; + hopefully other people do, too. + +* I worked on my Raspberry Pi HTTP-based music player a bunch. For me, the + person who programs it all, I split it into a bunch of different files; that + was done over the course of a series of messy git commits and computer-swaps. + I also made it so that the temporary song download now use actual tempfiles, + rather than hidden files that are semi-automatically deleted by the play.js + program - so now things are in general a lot cleaner. Before, pressing ^C + wouldn't get rid of the WAV for the currently playing file (nor the WAV for + the up-next song), so you'd end up with tons of leftover WAVs, quickly eating + up hundreds of megabytes (two new files every time you run the player!). + That's fixed now - *real* tempfiles, which I'm now using, are automatically + deleted by the system! + +* I made my music player use `util.promisify` for `fs` methods. So much for + `mz/fs` being relevant. + +* I read a short story about a kid who broke his leg and got it fixed, all + without getting any donations or funding from family friends (or others), + and his not-particularly-rich family didn't even have any financial stress, + period. I won't spoil the trick - [read it yourself!][one-free-solution] + + Okay, but seriously, it's kind of interesting to think about not having any + sort of fund-raiser needed. I never really thought of the stress that must + come, you know, financially, in places where healthcare isn't free. I guess + it's kind of taken for granted until you've got experience with a place + that doesn't have free healthcare. + + [scrap]: http://towerofnix.github.io/scrap-mod/ + [baiyon]: http://baiyon.com/en/ + [pj-eden]: https://www.youtube.com/playlist?list=PLB0A37E2F550DB027 + [C418]: https://c418.org/ + [185]: https://c418.bandcamp.com/track/185 + [eevee]: https://eev.ee/ + [introspection]: https://eev.ee/blog/2017/05/28/introspection/ + [weekly-update]: https://eev.ee/dev/2017/05/28/weekly-roundup-in-flux/ + [smw]: https://www.youtube.com/watch?v=Ixu8tn__91E + [time-mop]: https://www.youtube.com/watch?v=DofhF-2sg1o + [billwurtz]: http://www.billwurtz.com/ + [grandia]: https://www.reddit.com/comments/6een02/ + [speed-of-light]: https://www.youtube.com/watch?v=msVuCEs8Ydo + [space-time]: https://www.youtube.com/channel/UC7_gcs09iThXybpVgjHZ_7g + [one-free-solution]: http://www.upworthy.com/when-her-5-year-old-broke-his-leg-this-mom-raised-0-its-actually-inspiring diff --git a/site/posts/13-productivity-idea.md b/site/posts/13-productivity-idea.md new file mode 100644 index 0000000..6d20ad5 --- /dev/null +++ b/site/posts/13-productivity-idea.md @@ -0,0 +1,21 @@ + + title: "Productivity idea" + permalink: '13-productivity-idea' + date: {m: 6, d: 7, y: 2017} + categories: + - 'text' + +--- + +# Productivity idea + +Maybe it'd be easier to accomplish things if the objectives were less general. +"Draw a picture" is obviously less specific than "draw a picture to work on +figuring out how character lighting works". Importantly, looking at a goal kind +of makes you think about how you haven't gotten the goal already. I think that +"draw a picture, because you haven't drawn one in ages" is less, well, harsh, +than "work on character lighting, because it'd be nice to be better at that". +(Sure, you *could* instead say "work on character lighting, because you're +terrible at it" or you *could* say "draw a picture, because it'd be nice to +draw things more frequently"; but those are the thoughts that come to *my* +mind.) diff --git a/site/posts/14-inconspicuous-update.md b/site/posts/14-inconspicuous-update.md new file mode 100644 index 0000000..bc0c98e --- /dev/null +++ b/site/posts/14-inconspicuous-update.md @@ -0,0 +1,91 @@ + + title: "Inconspicuous Update" + permalink: '14-inconspicuous-update' + date: {m: 6, d: 13, y: 2017} + categories: + - 'text' + - 'dev' + +--- + +# Inconspicuous Update + +Writing about the things I've done is fun, and maybe even interesting to some, +so since I wrote a particularly in-depth entry for today's occurings, I figured +I'd share that. Onwards! + +--- + +* I read about Twitter's lovely new "shadow banning" feature. Essentially it's + this new automated mechanic that blocks Twitter accounts from being seen by + others, without telling anyone. You can imagine the trouble this would cause + when somebody's following someone else, and they aren't getting notifications + for the mentions written by a person they're following..! There's a whole lot + of other gook related to the issue; + [this thread was informative.](https://twitter.com/Aichmomanic/status/874160155960647681) + +* I worked on my English course. I'm done The Old Man and the Sea; now I'm + learning about writing a biography. It's not going to be an /auto/biography + (that is, I won't be the subject); I'm not sure whether that'll make it + easier or not. The assignment lets you pick whoever you want. (In fact, + that's essentially the entire current assignment - pick somebody!) + I picked AntVenom, because, unlike most other YouTubers, his life on the + YouTube website has been somewhat tumultuous. (To be fair, the assignment + didn't ask for a YouTuber specifically; but AntVenom works well since his + life has been somewhat more interesting than the usual (or is it conceptual?) + rainbows and sunshine filled career.) + +* I worked on my Khan Academy algebra. I'm not sure how much progress I + actually made - it looks like I mostly did things I'd already practiced + before - but hey. It's good to keep the mind fresh by revisting old things. + +* I experimented with my Raspberry Pi; this time I configured it to not use + the somewhat bloated desktop environment, in favor of a simple console-only + display. I'm not sure if that's actually improved much performance-wise, + but intuitively it should, and at least I don't have to worry about there + being some gigantic UI sitting around not being used when I'm only SSHing + into the device! + + (I also tried out tmux; it's as slightly confusing as it's always been. I + tried the Lynx browser, but it seems to be missing some basic browser + features on the surface, such as scrolling by a single line rather than by a + whole page; and naturally it doesn't work perfectly with every site. On + another note, I figured out how to get my stupid gray ethernet cable out of + whatever device it's plugged into - you only need to push down on its grip + part and pull out! That should have been obvious from the start. Oh well; at + least I know now.) + +* I read about the + [C Seed 262-inch TV](http://uncrate.com/article/c-seed-262-inch-tv/). + It's quite the large TV..! Too bad the only pictures of it didn't compare it + to, you know, an ordinary-sized room. It's not as easy to get a sense of how + huge it is when your only reference is a few couches and a table. + +* I took a look at some old Scratch projects that I favorited on my original + Scratch account. Hm, did I say something about keeping the mind fresh by + revisiting old things..? + +* I made [a tiny music visualizer](https://scratch.mit.edu/projects/165923925/) + in Scratch. + +* I got a bit of an idea on using microphones and speakers to communicate + between a DS and a Raspberry Pi. The basic concept is that the DS has a + built-in microphone, which we can use to send data to the DS, and an + auxiliary output cable slot, which we can use to get data from the DS. Then + we can make the DS and some other device (like the Raspberry Pi) talk to + each other! (Too bad it's not wireless. It seems like it should be possible + to dos something like this wirelessly, since the DS does have some sort of + Wi-Fi support, but it doesn't seem to really be standard. Apparently it + requires some Nintendo service to be online, which hasn't been since 2015..?) + +* I considered printing all of + [Piers Anthony's newsletter](http://hipiers.com/backissues.html). I'd love + to have it all as a book; it'll take a ton of work, if I do decide to do + that, though! (In theory it'd be best to just automate the process, but then + you run into issues such as - what if there's a table embedded? What if + there's something that simply NEEDS human notice to be dealt with well? So + doing it by hand is kind of required. It also seems like there's just a bit + of merit to going through all of his posts by hand; though I'll mostly be + copy-pasting. Or who knows. Maybe I'll go and type it all out manually. Wait, + no, that's a terrible idea - each of his posts are several thousands of + words long, and he's written well over a hundred and fifty posts! Yikes.) diff --git a/site/posts/15-i-accidentally-drew-some-things.md b/site/posts/15-i-accidentally-drew-some-things.md new file mode 100644 index 0000000..9633e6a --- /dev/null +++ b/site/posts/15-i-accidentally-drew-some-things.md @@ -0,0 +1,34 @@ + + title: "I accidentally drew some things" + permalink: '15-i-accidentally-drew-some-things' + date: {m: 6, d: 25, y: 2017} + thumbnail: 'static/media/15-snek-expressions.png' + presentArt: true + categories: + - 'art' + +--- + +# I accidentally drew some things + +I found myself in a place where my only items on hand were a note-pad and a +pencil. As it would turn out, "the amount of in-this-situation I am" and "the +amount of art I make" is directly proportionate; so I ended up drawing a few +sketches: + +![Snake thing world](static/media/15-snek-world.png) + +![Various expressions on snake things](static/media/15-snek-expressions.png) + +![Insect thing versus a fruit](static/media/15-insect-vs-fruit.png) + +![Humanoids are a joke](static/media/15-humanoids-are-a-joke.png) + +![Humanoids are still a joke](static/media/15-humanoids-are-still-a-joke.png) + +![Humanoids are STILL a joke](static/media/15-humanoids-are-still-still-a-joke.png) + +I guess I also painted something a little while earlier; I only used a single +color because I have *no* idea how color blending works. + +![Colors are outdated](static/media/15-colors-are-outdated.png) diff --git a/site/posts/16-on-socialization.md b/site/posts/16-on-socialization.md new file mode 100644 index 0000000..0a82864 --- /dev/null +++ b/site/posts/16-on-socialization.md @@ -0,0 +1,138 @@ + + title: "On socialization, and such" + permalink: '16-on-socialization' + date: {m: 6, d: 27, y: 2017} + categories: + - 'text' + +--- + +# On socialization, and such + +## An issue + +Say I've got a friend online who sent a message to me. It'd be something +simple, just like, "Hi!" + +Okay, this message was sent.. half an hour ago. I missed it, because I wasn't +on Discord or Twitter or email or whatever their client of choice was. Oops. +Now I've got two options: + +* **Respond!** Duh, one of my friends said hi, so I'll say hi back. + +* **Procrastinate.** Wait, what? Well, they sent the message half an hour ago.. + so it's not *that* bad if I don't get back to them right now.. hold on, why + am I even thinking about this? But still.. + +Alright, so, I imagine most people would go with the first option. Right? +Because it makes *sense* to go and respond when someone says hi, plus typically +conversations are good, anyways. They're enjoyable! + +..But I procrastinate. I know it's true that having a casual discussion with +someone is basically always good, but I choose to wait, anyways? + +And I'm not even *doing* anything! At that point in time I'm probably just +sitting around, doing nothing of particular interest. I *know* it'd be more fun +(and productive) to have a chat. But I delay it, for some reason? + +And this is consistently something true of me. + +* I got an idea to work on a project with somebody the previous day.. and now, + even though I know I'd still like to work on that project with them, I delay. + +* One of my friends said hello a while earlier. I choose to ignore them. + +* One of my friends said hello *now*, while I'm online. It's a lot less easy + to ignore someone when they're talking to you while you're basically present, + so, you know, I go and respond ("Hi!"), but there's still a bit of a feeling + of "no, no, hold on, ugh." + + (This is true for whenever I'm talking to someone in a direct/private + message online, mind you. It's not just some one person in particular!) + +* We've got visitors; they only visit once or twice a year, so it's pretty + special for both us and them. But before they arrive, there's a little + feeling of dread.. even though I know their visits are always completely + enjoyable! Once they get here, though, that feeling disappears. + + (Again, this is true for *any* visitors. Some people had come from quite + far away - across an ocean, in fact! - to visit us, and, you know, that + feeling of uncertainty (I guess?), before they arrived was still there.) + +It doesn't even have to be related to socializing, actually. Drawing art is +another example of when this is an issue for me. I *know* I enjoy drawing! +And yet I procrastinate. It doesn't seem to me like there's any *reason* to +delay the inevitable *enjoyment* I'll get out of making a piece of art, +especially when I tend to delay many other unrelated things I also enjoy. + +I know this is also a problem for others. It certainly doesn't seem like it's +an issue for *most* others, though! + +I'm not entirely sure how to deal with it. It's frustrating, because it *is* a +bit of a real problem for me. It seriously does get in the way of my drawing - +it's definitely the reason I haven't made much of any art in the last few +weeks. It's gotten in the way of lots of projects I want to work on, with +myself, or with others. + + +## Internet ritual + +Open your browser. Check what's new on Twitter. Check if anybody's uploaded an +interesting video to YouTube. Catch up with Facebook. Check the notifications +you've got on any forum you browse. See what people are up to on that one +subreddit. (Maybe even check the /new section.) + +Doing what you want to, doing what you need to, socializing - it's all hard. +Procrastination is easy. Why would you bother doing something that takes any +effort or willpower when you could do something that *doesn't?* + +And it's *so* easy. One option is tough; one isn't. *You* don't even need to +make the choice consciously; chances are your brain will for you. And then go +with that, right? + +Chances are you don't even *want* to do that one thing you claim you do. Oh, +yeah! You know how there's that one person that just messaged you? And how +you've meant to talk to them? ..But you don't. Responding is hard. You tell +yourself you enjoy discussing things with people.. but that's not a simple +task; do you *really* enjoy it? + +Procrastinate, instead. + +Maybe don't even respond at all. + + +## Or not? + +It's not easy to get out of those thoughts. You've literally already convinced +yourself that it'd just be easier to stick to procrastinating, to stick to that +consistently easy, simple, internet ritual. They aren't even always conscious +thoughts; it's just, "oh, hmm, may as well check what's new!" + +But they're *intrusive.* Obviously they do get in the way of your life, and the +things you *do* enjoy. Before talking to someone, it's easy to convince +yourself that you don't really enjoy it. But you clearly *do* like talking to +people; it's quite possible that you've talked to this very person before, and +enjoyed it! + +They get in the way of doing other things, too, of course - why create +something when it's so much easier to just.. not? And yet I *do* love drawing +things! It's the best feeling I get! + +It's now been a week since I wrote the first section of this post. The other +day I was talking to a friend of mine about all this; that lead to me making +the decision to kill the so-called internet ritual as best I could. So I've +been working on that, myself. + +Will that magically solve all my problems? Will it make talking to people much +easier, suddenly? Probably not! But maybe it'll help. I *think* it will; it's +been somewhat helpful so far. I've gotten a bit better at following my basic +goals since I did, and I think I've also felt a little better about myself i +general. + +Call it all a placebo effect if it is, but I guess it's enough, anyways. If I'm +feeling better about myself and finding it easier to do what I want and need +to, it's working. Who knows how it'll go from here? + +(PS, while writing the later parts of this, I've been listening to +[Dief](https://c418.bandcamp.com/album/dief), which I've found to be shockingly +appropriate background music for this post!) diff --git a/site/posts/17-fractions-and-simplification-explained.md b/site/posts/17-fractions-and-simplification-explained.md new file mode 100644 index 0000000..e59bd81 --- /dev/null +++ b/site/posts/17-fractions-and-simplification-explained.md @@ -0,0 +1,415 @@ + + title: "Fractions and simplification, explained" + permalink: '17-fractions-and-simplification-explained' + date: {m: 7, d: 1, y: 2017} + categories: + - 'text' + +--- + +# Fractions and simplification, explained + +I've always found fractions to be a little bit magical; things like "doing the +same multiplication to the numerator and denominator gets you a fraction of +equal value" and the process of simplifying fractions have been useful, but it +hasn't been very clear *how* they work. So in this I set out on the silly, +little, but fun quest to do define the way fractions work using plain old +math.. + +--- + +A *fraction* is sort of like a number made of two parts. For example, we might +have a fraction made of the numbers 1 and 2, and we would write that as +<code class='math' id='math-frac-1_2'>\frac{1}{2}</code>. We could have another +fraction made of the numbers 7 and 9, and that would be written as +<code class='math' id='math-frac-7_9'>\frac{7}{9}</code>. + +The *value* of a fraction is simply the fraction's top number (we call this its +*numerator*) divided by its bottom number (the *denominator*): +<code class='math' id='math-fracval-9_3'>\frac{9}{3} = 9 \div 3 = 3</code>, +<code class='math' id='math-fracval-100_20'>\frac{100}{20} = 100 \div 20 = 5 +</code>, +and so on. (To *evaluate* a fraction just means to replace that fraction with +its value.) + +--- + +You can do lots of cool things with fractions, but we're interested in +*multiplying* them. + +Hopefully you already know how to multiply two normal numbers - for example, +you should already know that +<code class='math' id='math-mul-3x7'>3 \times 7 = 21</code>, +<code class='math' id='math-mul-2x4'>2 \times 4 = 8</code>, and so on. + +Multiplying two fractions is actually pretty simple - we just need to multiply +the two numerators and the two denominators together, and we get another +fraction. For example, to multiply +<code class='math' id='math-multiply-two-fractions-statement'> + \frac{1}{2} \times \frac{4}{3} +</code>: + +<pre class='math' id='math-multiply-two-fractions-solution'> + \frac{1}{2} \times \frac{4}{3} = \frac{1 \times 4}{2 \times 3} = \frac{4}{6} +</pre> + +We can also multiply other fractions: + +<pre class='math' id='math-multiply-two-fractions-2'> + \frac{7}{5} \times \frac{2}{100} = \frac{7 \times 2}{5 \times 100} = \frac{14}{500} +</pre> + +<pre class='math' id='math-multiply-two-fractions-3'> + \frac{4}{6} \times \frac{1}{3} = \frac{4 \times 1}{6 \times 3} = \frac{4}{18} +</pre> + +..and so on. + +(We've already said that fractions can be made out of two numbers. But we made +the fraction +<code class='math' id='math-frac-1x4-2x3'>\frac{1 \times 4}{2 \times 3}</code> +above - how did that work? Well, it makes sense if you think of +<code class='math' id='math-expr-2x3'>2 \times 3</code> and +<code class='math' id='math-expr-1x4'>1 \times 4</code> +as numbers. In fact you don't need to worry about what those values are (even +though we know them to be 6 and 4); just that they can be used in place of +numbers. If you would like to get very technical, we could say that a fraction +is made of two *expressions*; and an expression is just a number, like 7, or a +calculation, like <code class='math' id='math-expr-6x5'>6 \times 5</code> or +<code class='math' id='math-expr-3+7'>3 + 7</code>.) + +--- + +What if we want to multiply a fraction and a normal whole number? + +<pre class='math' id='math-frac-1-2-x4'> + \frac{1}{2} \times 4 +</pre> + +The trick is that you need to turn the whole number into a fraction, somehow. +The fraction we are creating must have an *equal value* to our whole number, +or else we cannot use it in place of that number. + +It is true that for any number, dividing that number by 1 does not change it: +<code class='math' id='math-5div1-eq-5'>5 \div 1 = 5</code>, +<code class='math' id='math-4div1-eq-4'>4 \div 1 = 4</code>, etc. Since we +already know that the value of a fraction is gotten by dividing the numerator +by the denominator, we can create a fraction from any division problem: simply +use the first number (5, 4, etc.) as the numerator and the second number (1) +as the denominator. +<code class='math' id='math-5-eq-5div1'>5 = \frac{5}{1}</code>, +<code class='math' id='math-4-eq-4div1'>4 = \frac{4}{1}</code>, and so on. + +Now we know that any number <code class='math' id='math-x4'>\times 4</code> +is equal to that same number +<code class='math' id='math-x-frac-4-1'>\times \frac{4}{1}</code>, so we may +put our <code class='math' id='math-frac-4-1'>\frac{4}{1}</code> fraction +into our calculation: + +<pre class='math' id='math-using-frac-4-1'> + \frac{1}{2} \times 4 = \frac{1}{2} \times \frac{4}{1} +</pre> + +And we already know how to multiply two fractions: + +<pre class='math' id='math-multiplying-frac-4-1'> + \frac{1}{2} \times \frac{4}{1} = \frac{1 \times 4}{2 \times 1} = \frac{4}{2} +</pre> + +--- + +We're almost ready to try out something interesting, but first we need to +understand one more concept - that any number multiplied by 1 is the original +number: <code class='math' id='math-4x1-eq-4-again'>4 \times 1 = 4</code>, +<code class='math' id='math-3x1-eq-3-again'>3 \times 1 = 3</code>, +<code class='math' id='math-999x1-eq-999-again'>999 \times 1 = 999</code>. +(We already know this from how multiplying whole numbers always works, of +course.) + +We can apply the same concept to fractions using what we now know about +multiplying a fraction by a whole number: + +<pre class='math' id='math-using-frac-1-1'> + \frac{4}{7} \times 1 = \frac{4}{7} \times \frac{1}{1} = \frac{4 \times 1}{7 \times 1} = \frac{4}{7} +</pre> + +This makes sense because we know that +<code class='math' id='math-1-eq-frac-1-1'>1 = \frac{1}{1}</code> (from our +rule that for any number, we can make a fraction that is equal to that number +by using it as the numerator of the fraction and 1 as the denominator). + +--- + +We can proceed now to define another rule: that for any fraction where the +numerator and denominator are the same, the fraction is equal to 1. For +example, <code class='math' id='math-frac-5-5-eq-1'>\frac{5}{5} = 1</code>, +<code class='math' id='math-frac-1-1-eq-1'>\frac{1}{1} = 1</code>, +<code class='math' id='math-frac-105-105-eq-1'>\frac{105}{105} = 1</code>. + +This makes sense because we know that any number divided by itself equals 1 +(that is, that any number "fits into" itself exactly 1 time). Using our rule +for the value of a fraction, we may write: +<code class='math' id='math-5div5'>\frac{5}{5} = 5 \div 5 = 1</code>, +<code class='math' id='math-1div1'>\frac{1}{1} = 1 \div 1 = 1</code>, and so +on. + +This means that we know how to replace any 1 in our calculations with a +fraction of equal value; let's try that with our "multiplication by 1 equals +itself" rule using a fraction: + +<pre class='math' id='math-double-fraction-parts'> + \begin{align*} + \frac{3}{5} \times 1 &= \frac{3}{5} \\ + \frac{3}{5} \times \frac{2}{2} &= \frac{3}{5} \\ + \frac{3 \times 2}{5 \times 2} &= \frac{3}{5} \\ + \frac{6}{10} &= \frac{3}{5} + \end{align*} +</pre> + +Ah! How peculiar. This reveals that the fraction +<code class='math' id='math-frac-6-10'>\frac{6}{10}</code> is actually equal +to the fraction <code class='math' id='math-frac-3-5'>\frac{3}{5}</code>. + +In fact, we can multiply any fraction and 1, or an equal value, and get a new +fraction that is equivalent to the first fraction: + +<pre class='math' id='math-frac-7-9-times-frac-3-3'> + \begin{align*} + \frac{7}{9} \times 1 &= \frac{7}{9} \\ + \frac{7}{9} \times \frac{3}{3} &= \frac{7}{9} \\ + \frac{7 \times 3}{9 \times 3} &= \frac{7}{9} \\ + \frac{21}{27} &= \frac{7}{9} + \end{align*} +</pre> + +<pre class='math' id='math-frac-4-3-times-frac-5-5'> + \begin{align*} + \frac{4}{3} \times 1 &= \frac{4}{3} \\ + \frac{4}{3} \times \frac{5}{5} &= \frac{4}{3} \\ + \frac{4 \times 5}{3 \times 5} &= \frac{4}{3} \\ + \frac{20}{15} &= \frac{4}{3} + \end{align*} +</pre> + +<pre class='math' id='math-frac-99-150-times-frac-1-1'> + \begin{align*} + \frac{99}{150} \times 1 &= \frac{99}{150} \\ + \frac{99}{150} \times \frac{1}{1} &= \frac{99}{150} \\ + \frac{99 \times 1}{150 \times 1} &= \frac{99}{150} \\ + \frac{99}{150} &= \frac{99}{150} + \end{align*} +</pre> + +<pre class='math' id='math-frac-2-4-times-frac-2-2'> + \begin{align*} + \frac{2}{4} \times 1 &= \frac{2}{4} \\ + \frac{2}{4} \times \frac{2}{2} &= \frac{2}{4} \\ + \frac{2 \times 2}{4 \times 2} &= \frac{2}{4} \\ + \frac{4}{8} &= \frac{2}{4} + \end{align*} +</pre> + +..And so on. + +Now - we have already said that for any fraction where the top and bottom are +equal, that fraction is equal to 1. What if we put two equal fractions inside +of our fraction? + +<pre class='math' id='math-nested-fraction'> + \frac{\frac{3}{9}}{\frac{3}{9}} +</pre> + +This is still equal to 1, because the numerator and denominator of the +"big" fraction are equal. (It *is* true that +<code class='math' id='math-frac-3-9-eq-frac-3-9'>\frac{3}{9} = \frac{3}{9}</code>, +of course!) + +Likewise, fractions such as +<code class='math' id='math-frac-frac-1-2-frac-1-2'>\frac{\frac{1}{2}}{\frac{1}{2}}</code> +and +<code class='math' id='math-frac-frac-5-4-frac-5-4'>\frac{\frac{5}{4}}{\frac{5}{4}}</code> +are also equal to 1. + +--- + +Now we can use all we've learned so far to try this: + +<pre class='math' id='math-frac-8-4-times-halfer'> + \begin{align*} + \frac{8}{4} \times 1 &= \frac{8}{4} \\ + \frac{8}{4} \times \frac{\frac{1}{2}}{\frac{1}{2}} &= \frac{8}{4} + \end{align*} +</pre> + +But how do we multiply those two fractions? Well, we can multiply them exactly +the way we would usually multiply fractions - multiply the two numerators +together and the two denominators together: + +<pre class='math' id='math-mix-frac-8-4-times-halfer'> + \frac{8}{4} \times \frac{\frac{1}{2}}{\frac{1}{2}} = + \frac{8 \times \frac{1}{2}}{4 \times \frac{1}{2}} +</pre> + +We already know how to multiply a whole number and a fraction together - just +convert the whole number to a fraction by putting the whole number on top and 1 +on the bottom: + +<pre class='math' id='math-solve-frac-8-4-times-halfer'> + \frac{8 \times \frac{1}{2}}{4 \times \frac{1}{2}} = + \frac{\frac{8}{1} \times \frac{1}{2}}{\frac{4}{1} \times \frac{1}{2}} = + \frac{\frac{8 \times 1}{1 \times 2}}{\frac{4 \times 1}{1 \times 2}} = + \frac{\frac{8}{2}}{\frac{4}{2}} +</pre> + +We are nearly completed; we may simply insert this fraction back into our +calculation: + +<pre class='math' id='math-frac-substitute-halved-fraction'> + \begin{align*} + \frac{8}{4} \times \frac{\frac{1}{2}}{\frac{1}{2}} &= \frac{8}{4} \\ + \frac{\frac{8}{2}}{\frac{4}{2}} &= \frac{8}{4} + \end{align*} +</pre> + +In order to actually make this useful, we must evaluate the fractions that are +in the top and bottom of our newly-created "big" fraction: + +<pre class='math' id='math-frac-solve-halved-fraction'> + \frac{\frac{8}{2}}{\frac{4}{2}} = \frac{8 \div 2}{4 \div 2} = \frac{4}{2} +</pre> + +And then we may put this back into our main calculation: + +<pre class='math' id='math-simplify-conclusion'> + \begin{align*} + \frac{\frac{8}{2}}{\frac{4}{2}} &= \frac{8}{4} \\ + \frac{4}{2} &= \frac{8}{4} + \end{align*} +</pre> + +As you can see, we have gone from one fraction, in this case +<code class='math' id='math-frac-8-4'>\frac{8}{4}</code>, to a fraction of +equal value but smaller numerators and denominators, +<code class='math' id='math-frac-4-2'>\frac{4}{2}</code>. This is what is +known as *simplifying* a fraction. + +We can show these are of equal value simply by comparing their values: +<code class='math' id='math-frac-8-4-eq-2'>\frac{8}{4} = 8 \div 4 = 2</code>, +and +<code class='math' id='math-frac-4-2-eq-2'>\frac{4}{2} = 4 \div 2 = 2</code>. +We say that because the fractions have an equal value, they are *proportional*. + +--- + +The *greatest common divisor* of two numbers is the greatest whole number you +can divide both numbers by and get two resulting whole numbers. There are +various methods of finding it; we write it with the notation +<code class='math' id='math-gcd-a-b'>\DeclareMathOperator{\gcd}{gcd} \gcd(a, b)</code> +(where <code class='math' id='math-variable-a'>a</code> and +<code class='math' id='math-variable-b'>b</code> are whole number values). For +example, +<code class='math' id='math-gcd-20-15'>\DeclareMathOperator{\gcd}{gcd} \gcd(20, 15) = 5</code> +because dividing <code class='math' id='math-20div5'>20 \div 5 = 4</code> and +<code class='math' id='math-15div5'>15 \div 5 = 3</code> both get us whole +numbers (4 and 3), and there is no greater number that both 20 and 15 can be +divided by to get whole numbers. + +The greatest common factor can be used inside of fraction simplification to +get the "completely" simplified value of any fraction. For example: + +<pre class='math' id='math-gcf-completely-simplified'> + \begin{equation} + \begin{split} + \frac{70}{42} &= \frac{70}{42} \times 1 \\ + &= \frac{70}{42} \times \frac{\frac{1}{\gcd(70,42)}}{\frac{1}{\gcd(70,42)}} \\ + &= \frac{70}{42} \times \frac{\frac{1}{14}}{\frac{1}{14}} \\ + &= \frac{70 \times \frac{1}{14}}{42 \times \frac{1}{14}} \\ + &= \frac{5}{3} + \end{split} + \end{equation} +</pre> + +(I skipped a couple of steps in multiplying the values in the "big" fraction +to keep a focus on the important part, which was applying the greatest common +divisor.) + +Another example: + +<pre class='math' id='math-gcf-simplify-frac-8-4'> + \begin{equation} + \begin{split} + \frac{8}{4} &= \frac{8}{4} \times 1 \\ + &= \frac{8}{4} \times \frac{\frac{1}{\gcd(8,4)}}{\frac{1}{\gcd(8,4)}} \\ + &= \frac{8}{4} \times \frac{\frac{1}{4}}{\frac{1}{4}} \\ + &= \frac{8 \times \frac{1}{4}}{4 \times \frac{1}{4}} \\ + &= \frac{2}{1} \\ + &= 2 + \end{split} + \end{equation} +</pre> + +This time we get the value of +<code class='math' id='math-frac-2-1'>\frac{2}{1}</code>, which is 2, and use +that as our simplified value. + +--- + +All of the above can be written in elegant and general algebra-like math. + +**Value of a fraction:** + +<pre class='math' id='math-value-of-a-fraction'> + \frac{a}{b} = a \div b +</pre> + +**Fractions from values using denominator 1:** + +<pre class='math' id='math-fractions-from-values-using-denominator-1'> + n = n \div 1 = \frac{n}{1} +</pre> + +**Multiply two fractions:** + +<pre class='math' id='math-multiply-two-fractions'> + \frac{a}{b} \times \frac{c}{d} = \frac{a \times c}{b \times d} +</pre> + +**Multiply a fraction and a value:** + +<pre class='math' id='math-multiply-fraction-and-value'> + \frac{a}{b} \times n = + \frac{a}{b} \times \frac{n}{1} = + \frac{a \times n}{b \times 1} = + \frac{a \times n}{b} +</pre> + +**Simplify a fraction (completely):** + +<pre class='math' id='math-completely-simplify-fraction'> + \begin{equation} + \begin{split} + \frac{a}{b} &= \frac{a}{b} \times 1 \\ + &= \frac{a}{b} \times \frac{1 \div \gcd(a,b)}{1 \div \gcd(a,b)} \\ + &= \frac{a \times 1 \div \gcd(a,b)}{b \times 1 \div \gcd(a,b)} \\ + &= \frac{a \div \gcd(a,b)}{b \div \gcd(a,b)} + \end{split} + \end{equation} +</pre> + +Alternate: + +<pre class='math' id='math-completel-simplify-fraction-alt'> + \begin{equation} + \begin{split} + \frac{a}{b} &= \frac{a}{b} \times 1 \\ + &= \left( \frac{a}{b} \times \frac{\frac{1}{\gcd(a,b)}}{\frac{1}{\gcd(a,b)}} \right) \\ + &= \left( \frac{a \times \frac{1}{\gcd(a,b)}}{b \times \frac{1}{\gcd(a,b)}} \right) \\ + &= \left( \frac{\frac{a}{1} \times \frac{1}{\gcd(a,b)}}{\frac{b}{1} \times \frac{1}{\gcd(a,b)}} \right) \\ + &= \left( \frac{\frac{a \times 1}{1 \times \gcd(a,b)}}{\frac{b \times 1}{1 \times \gcd(a,b)}} \right) \\ + &= \left( \frac{\frac{a}{\gcd(a,b)}}{\frac{b}{\gcd(a,b)}} \right) + \end{split} + \end{equation} +</pre> + +(The parentheses around each step are only present to clarify the separate +steps; they don't actually mean anything.) diff --git a/site/posts/18-disqus-comments.md b/site/posts/18-disqus-comments.md new file mode 100644 index 0000000..a574c35 --- /dev/null +++ b/site/posts/18-disqus-comments.md @@ -0,0 +1,92 @@ + + title: "There goes using Disqus comments" + permalink: '18-disqus-comments' + date: {m: 7, d: 5, y: 2017} + categories: + - 'text' + - 'dev' + +--- + +# There goes using Disqus comments + +I've removed Disqus comments. + +The main initiative for this decision is that they're now going to be adding +advertisements to some sites. + +There are a few reasons why it's tempting to *keep* using Disqus, actually: + +* These new advertisements only affect large, commercial sites. They explicitly + aren't going to be adding any ads to personal blogs, in fact. + +* The advertisements aren't even going to be the usual, intrusive, clickbait + things you'll find on any other site - instead they'll be ads promoting new + Disqus features. + +But I'm still getting rid of Disqus comments. + +First off, the fact that they can and *will* change things like this is +appalling. Disqus has always been that awesome, free, ad-less discussion tool +anybody can use just by embedding it on their website, hardly any setup needed. +They aren't going back on much of that *per se*, but.. it's just kind of like +they're going from being that one not-for-profit tool to being for-profit, all +of a sudden, you know? + +This whole thing feels like if Twitter decided to embed ads inside your +profile, assuming your Twitter account has over (for example) 75,000 followers, +with an option that those people with 75K+ followers pay a subscription to get +rid of them. + +Or maybe it's more like if Streamable decided to stick advertisements on the +sides of your videos once they get 10,000 views. + +Because, you know, we trust them? We trust those sites not to mess with how we +use them or how people see the things we make through them. But clearly that +isn't actually reliable.. and that's kind of frustrating, and not something I +want to get stuck in. + +Honestly, this is all more worrying for *bigger* sites. There was only a couple +of discussions done through Disqus on this blog; other sites have *so many* +more. I don't know how they'll deal with it.. it looks like they'll either have +to abandon and obliterate old comments, or be forced into either paying for +Disqus or ending up have ads. + +(Or some sort of compromise. Other options include backing up all Disqus +threads, but I'm not sure that's possible without writing a Crazy Hack. Another +option would be only displaying Disqus threads on old posts, but that's not +really wonderful, either.) + +## How can we comment now?? + +So.. there's no incredible option yet. What *are* fine options for the time +include: + +* Write me an email. I'm not going to embed it here, but suffice it to say that + it doesn't take much effort to guess; also, it's an at-gmail.com. Seriously. + Virtually no effort to find. + +* Send me a tweet. My account's [@towerofnix][twitter] there (surprised?); + those are (slightly) more public than emails, which is probably good, but + also limited to 140 characters (or - ugh - chunks of 140 characters). + +* Write me a Scratch comment. My username is [_nix][scratch] (with an + underscore before "nix"). I guess this is handy if you're one of the few + people in the world who has a Scratch account, but then, chances are most of + the people who view my blog are them. + +All of those are imperfect for various reasons. And sorry for removing Disqus +comments. I know it got rid of a good discussion on radios. (Just kidding, [I +saved that!][radios] No reason not to.) It'll probably make commenting less +easy in the future, until I find some better solution than I listed above. +(No, you don't get to pressure me to do that *now*, thank you!) + +As a side effect of removing Disqus comments, this site is now 100% +JavaScript-free. Bwahahahaha. Run this on all your stupid browsers! (Er, just +don't look at [the last post][fractions] through curl or view source; I haven't +figured out how to embed SVGs well yet!) + + [fractions]: posts/17-fractions-and-simplification-explained.html + [twitter]: https://twitter.com/towerofnix + [scratch]: https://scratch.mit.edu/users/_nix/ + [radios]: static/media/18-radio-discussion.png diff --git a/site/posts/19-belated-update.md b/site/posts/19-belated-update.md new file mode 100644 index 0000000..19d6f20 --- /dev/null +++ b/site/posts/19-belated-update.md @@ -0,0 +1,53 @@ + + title: "Belated update" + permalink: '19-belated-update' + date: {m: 8, d: 3, y: 2017} + categories: + - 'dev' + - 'text' + +--- + +# Belated update + +Oh dear, I haven't published anything to this blog in nearly an entire month! +Obviously that's my fault; sorry nothing's really come to mind. + +*However,* I did still do things in the last month; so, here's a simple little +summary of what comes to mind: + +* I worked on [http-music][http-music]. A [lot][http-music-commits]. + Interesting and important new features: + the entire playlist format has been changed (e.g. downloaders can be + manually specified per-track or [per-group][apply-example]); + it now supports [a bunch more sites][youtube-dl-supportedsites]; + playlists can be downloaded from websites; + playlists that are modified at runtime ("active" playlists) can be exported; + and SoX now works again, so you don't need to install mpv (which has been + *very* difficult to install, for me). + +* I put some effort into [a couple][promise-all] + [StackOverflow posts][redefine-private], but they were ignored. + +* I [celebrated][1500000000000] hitting Unix timestamp 1500000000. + +* I hacked around with my Raspberry Pi a bunch! My entire setup is now + (optionally) battery-powered, has a (small) monitor, and fits in a (hardly + portable) suitcase. [I wrote about my old setup a while ago;][pi-madness] + maybe it'd be potentially interesting to write about my new one! + +* I drew a few things on paper. I've come to the conclusion that drawing on + paper is *much* nicer for, you know, drawing ideas?? + +I also went on a vacation for half the month. It was nice. (I'm not sure if it +makes up for this somewhat-underwhelming list of interesting things I did, +though..!) + + [http-music]: https://github.com/towerofnix/http-music + [http-music-commits]: https://github.com/towerofnix/http-music/compare/master@%7B2017-07-01%7D...master@%7B2017-08-03%7D + [apply-example]: https://gist.github.com/towerofnix/cd7465a82c8b367eef221e61c3b6186e + [youtube-dl-supportedsites]: https://rg3.github.io/youtube-dl/supportedsites.html + [promise-all]: https://stackoverflow.com/a/44849645/4633828 + [redefine-private]: https://stackoverflow.com/a/45237361/4633828 + [1500000000000]: https://towerofnix.github.io/1500000000000/ + [pi-madness]: posts/11-raspberry-pi-madness.html diff --git a/site/posts/2-rewriting-it-again.md b/site/posts/2-rewriting-it-again.md new file mode 100644 index 0000000..a111704 --- /dev/null +++ b/site/posts/2-rewriting-it-again.md @@ -0,0 +1,39 @@ + + title: "Rewriting it Again" + permalink: '2-rewriting-it-again' + date: {m: 4, d: 12, y: 2017} + thumbnail: 'static/media/01-oops.png' + presentArt: true + categories: + - 'art' + - 'dev' + - 'text' + +--- + +# Rewriting it Again + +It's been brought to my attention that my blog requires JavaScript to operate +even the slightest reasonable amount. (It's true! Have you tried opening the +site with JavaScript disabled?) I agree that that's bad for a number of +reasons: + +* Anybody using a web browser without JavaScript has to go to the + *code repository* to read the posts. I repeat - *code repository*. + +* Most websites work without JavaScript enabled, and a blog certainly has no + good real-world excuse to require the language. + +* Having a single-page layout works really badly for a whole host of reasons, + including Google indexing, Disqus comment sharing, and jumping to an element + with an ID (you know, stuff like `index.html#contact`), which are all + relatively important things. + +So I'm rewriting the blog again. This time every update will be automatically +converted by my own computer into static HTML documents. I'm pretty sure that's +how most serverless blogs work. + +In the meanwhile, here's a quick picture of something that happened to one of +my characters when I failed to draw their hair well: + +![Oops, this was not intentional](static/media/01-oops.png) diff --git a/site/posts/20-on-confusion-related-to-my-usernames.md b/site/posts/20-on-confusion-related-to-my-usernames.md new file mode 100644 index 0000000..dbbf514 --- /dev/null +++ b/site/posts/20-on-confusion-related-to-my-usernames.md @@ -0,0 +1,84 @@ + + title: "On confusion related to my usernames" + permalink: '20-on-confusion-related-to-my-usernames' + date: {m: 10, d: 3, y: 2017} + categories: + - 'text' + +--- + +# On confusion related to my usernames + +There's been an awful lot of confusion as to which of my Scratch accounts are which, and what the purpose of each one is, so I'm feeling obliged to make a hopefully-helpful guide to it all. I probably should have sooner! + +Since my names and handles are rather more complicated than being limited to Scratch alone, I'm including some needed info on my names on other sites as well. + +Disclaimer: I spent two hours around midnight writing this; I haven't significantly re-read it. Also, you might be more confused *after* you read, since I probably didn't do a great job at explaining all this; feel free to ask about anything if you want (info on how to do that's at the bottom of this post, but too-long-didn't-scroll just comment on [fIorrie][scratch-florrie] or email me). + +--- + +## Early accounts + +Let's start at the beginning. In 2007, I created an account for the Poptropica site. I gave it the username `liam4`, since my name was Liam and I was four years old. + +In February 2013, a bit more than four and a half years ago, I created my first Scratch account, ginving it the username [`liam48D`][scratch-liam48D]. (This was, for all practical purposes, my first real existence on the internet, besides a few comments on some KhanAcademy videos.) I figured I'd take my Poptropica username and add a smiley face to the end; and such, `liam48D` became. + +In late 2014, I was getting more into text-based programming, so I created the GitHub account `liam4`. (The username was, obviously, the same as I'd taken everywhere else; I guess I didn't feel that the smiley was necessary.) + +## towerofnix + +Some time in 2015, I decided I wanted to make a separate user account on my laptop for myself to go through the activities and demos in [*Unix for the Beginning Mage*][ufbm], which I was reading, at the time, to learn basic shell usage. Having a separate account would keep the whole computer cleaner, and give me a fresh start. I gave the account the username `towerofnix`, which (of course) was based on the first paragraph of the book: + +> Hello, Young Mage! Welcome to the Tower of Nix! Here you'll begin your training in the skills of Unix. I'll be your Instructor and Narrator. + +The username towerofnix stuck, and I began using it just about everywhere. + +In February 2016, I found myself in need of another Scratch account, so that I could practice playing with [cloud data][cloud-data] more; so, I created the account [`_nix`][scratch-nix]. (Using `_nix` instead of `nix` was because the name actually came from "\*nix", in the sense of "any Unix-like" (obviously derived from towerofnix). Though I suppose it helped that `nix` was already taken.) + +A few months later (July of 2016), I created the Scratch user [`towerofnix`][scratch-towerofnix]. I created this account to make tutorials about programming with Scratch; I didn't go too far with it, though (only to [one project](https://scratch.mit.edu/projects/116852216/)). I also made this account to post projects I put more effort into, but by the time I completed any large projects I'd mostly abandoned the towerofnix account... + +...Because, around the time I made the towerofnix account, I'd been transitioning from using liam48D to \_nix. As I moved towards \_nix, I practically forgot about towerofnix; and I didn't really feel like separating my amazing projects from my simple test ones (in part because I wasn't actually completing any large ones!). + +Later I also made an email account, towerofnix-at-gmail, and a Twitter account, [@towerofnix][twitter]. Most accounts I have in other places (e.g. Stack Exchange) are also called towerofnix. I eventually renamed my GitHub account, liam4, to [towerofnix][github-towerofnix]. ([liam4][github-liam4] is now just a notice that I changed my username.) + +## Florrie + +Okay, so, all of the above *probably* makes sense. (Hopefully?) But here's the topic of confusion and interest that's always brought up - what on *earth* is Florrie? + +The simple answer is the objective one; "Florrie" is a name. It's the *name* I go by online; the idea is that, when you're thinking of me, you don't think "Nix" or "Tower of Nix", you think "Florrie". + +The name "Florrie"'s origin is.. sort of complicated. + +In online groups and such, I'd been going by a few names. For a while, I was using "Tonix". Tonix was made because I wanted (and needed) a name that was less of a mouthful than "towerofnix", and a name that people could actually think of when they think of me. + +But Tonix just wasn't *good*. Tonix is pronounced like "tonics", plural of tonic, and.. handy medicinal potions wasn't what I meant by the name. It also reminded me of the word "toxic", which has *definitely* never been the connotation I've wanted. + +Unfortunately, my other ideas weren't that much better. I noticed some people called me "Tower" when speaking, and another called me Nix, but both of those were hardly *names*. + +"Florrie" happened on accident. A friend of mine called me Florrie one day (online), and, as soon as I read it, the name resonated with me. + +Okay, so, hold on, now everyone's confused - why not just use *Liam* as a name for people to know me by online? I could make a claim that I don't want people to know that people call me Liam in real life; but that's not really true, because my early accounts were all named "liam"-something, and I haven't tried to hide them at all. But, online (and to people I've met online), I'd rather not be called Liam - either Florrie or towerofnix is fine. + +## fIorrie + +Another thing people are confused on - what's this [fIorrie][scratch-florrie] account? + +The reasoning behind the name is simple; it's just "florrie", but with the L replaced with an I, because florrie was already taken [over nine years ago](https://scratch.mit.edu/users/florrie/). (It's still pronounced "Florrie"!) + +The Florrie user was created as an experiment to see how people would react to it. In the end, it ended up as a separate space for slightly more personal discussion that I didn't want immediately on my \_nix page. [Have a look at its (earlier) comments!](https://scratch.mit.edu/users/fIorrie/#comments) (The comments are still public *so that* they can be read; I just didn't want them *immediately* on /users/\_nix.) + +--- + +So, ah, that's my long-overdue formal/informal answer to some of the confusion people have had. If you have any questions or confusions at all, *please* feel free to ask; I probably did a terrible job at explaining most of this, and directly answering questions is a bit easier (and maybe more helpful) than making something like this. + +There's no comment system on my blog, but you can email me (you did catch my email, right?) or comment on [the fIorrie Scratch profile][scratch-florrie]. (The Scratch page is better; I prefer public conversations! But I'm totally fine with email as well, if you'd prefer.) + + [scratch-liam48D]: https://scratch.mit.edu/users/liam48D/ + [scratch-nix]: https://scratch.mit.edu/users/_nix/ + [scratch-towerofnix]: https://scratch.mit.edu/users/towerofnix/ + [scratch-florrie]: https://scratch.mit.edu/users/fIorrie/ + [github-towerofnix]: https://github.com/towerofnix/ + [github-liam4]: https://github.com/liam4/ + [twitter]: https://twitter.com/towerofnix/ + [ufbm]: http://unixmages.com/ + [cloud-data]: https://wiki.scratch.mit.edu/wiki/Cloud_Data diff --git a/site/posts/21-digital-art-again.md b/site/posts/21-digital-art-again.md new file mode 100644 index 0000000..58d0aa4 --- /dev/null +++ b/site/posts/21-digital-art-again.md @@ -0,0 +1,23 @@ + + title: "Digital art, again!" + permalink: '21-digital-art-again' + date: {m: 10, d: 7, y: 2017} + thumbnail: 'static/media/21-ske.png' + presentArt: true + categories: + - 'art' + +--- + +# Digital art, again! + +![Art export](static/media/21-ske.png) + +I haven't drawn anything digitally for a while! Making this was pretty fun (more fun than my last experience with trying to draw something digitally, for sure). + +I wasn't originally aiming to make something with this sort of sketch-like style, but I like how it turned out. + +Here's the MediBang Paint [source file][source-file], if you're curious. I need to set up a proper license page - for now take the art I release as [Attribution-ShareAlike 4.0 International][license]! (Basically, you're free to make a remix of my picture, or use it in your own project, as long as you give (visible) credits to me for making it, or the original version of it (ideally with a link back here). You must also share the work which remixed or used mine under this same license, so other people can freely build on your remix, as well.) + + [source-file]: static/media/21-ske.mdp + [license]: https://creativecommons.org/licenses/by-sa/4.0/ diff --git a/site/posts/22-paper-art-dump.md b/site/posts/22-paper-art-dump.md new file mode 100644 index 0000000..03e613c --- /dev/null +++ b/site/posts/22-paper-art-dump.md @@ -0,0 +1,42 @@ + + title: "Paper art dump" + permalink: '22-paper-art-dump' + date: {m: 10, d: 31, y: 2017} + thumbnail: 'static/media/22-beken-kleur.png' + presentArt: true + categories: + - 'art' + +--- + +# Paper art dump + +Over the last month or so, I've drawn.. a few things. Not enough! - But here's some of what I did. + +![Sketch of ske (1)](static/media/22-sketch-of-ske-1.png) +![Sketch of ske (2)](static/media/22-sketch-of-ske-2.png) + +These two are somewhat early sketches of [the person I drew last time](posts/21-digital-art-again.html). Originally I'd actually been planning on a totally different style (and even a different character) for that picture; I like how it turned out, though! + +(Okay, now I *have* to explain - I had been thinking of a black-and-white (like, really 000000 and FFFFFF - no grayscale) digital drawing with \[character name\] sorta staring off like in those sketches. Who knows, maybe I'll try something like that some other time?) + +![Beken Kleur](static/media/22-beken-kleur.png) + +My sister asked me to find a person on Google images and draw them. I did. + +This drawing is based off of [Dr. Christophe van der Beken](http://www.hrc.ugent.be/staff/christophe-van-der-beken/). I just realized my written credits on the drawing refers to a "Christopher", but the guy here is named "Cristophe", so.. oops! (Also "Kleur" - I have no idea what I was reading when I wrote it???) + +![Flower bear thing?](static/media/22-nice-flower.png) + +This is a.. flower bear thing? In any case, it's a full character picture, which I haven't done many of. + +![Sleeping has been proven to be necessary](static/media/22-sleep.png) +![Not so good for comfort](static/media/22-sleep-please.png) + +Who needs sleep? This character, apparently, who is stuck in a house avoiding the 800-celsius, moonlit outside. Too bad their efforts don't help much. + +I drew this in the middle of the night. I actually wasn't having any trouble sleeping; I just wanted to draw something. + +--- + +I've found drawing with a plain old pencil and paper easier than on a computer. I'm scared to death of using color on a paper picture, though; probably actually trying it (and maybe using a non-erasable pen, rather than a pencil?) would help. diff --git a/site/posts/24-elian-scribblings.md b/site/posts/24-elian-scribblings.md new file mode 100644 index 0000000..a8938f2 --- /dev/null +++ b/site/posts/24-elian-scribblings.md @@ -0,0 +1,18 @@ + + title: "Elian scribblings" + permalink: '24-elian-scribblings' + date: {m: 11, d: 19, y: 2017} + thumbnail: 'static/media/24-elian-scribblings.png' + presentArt: true + categories: + - 'art' + +--- + +# Elian scribblings + +![How am I supposed to write an alt-text for this?](static/media/24-elian-scribblings.png) + +I was bored at a place I was visiting and decided to practice my Elian Script writing. Only a very wet marker and a napkin were available. The marker sort of leaked through the napkin, making it act like a pressure-sensitive pen on overdrive.. that was difficult, but also really interesting, to work with! + +None of this is outstanding, but it was fun to make anyways. You'll have to flip and rotate the picture to read it all, if you're curious. diff --git a/site/posts/25-browsing-the-passed.md b/site/posts/25-browsing-the-passed.md new file mode 100644 index 0000000..00369e4 --- /dev/null +++ b/site/posts/25-browsing-the-passed.md @@ -0,0 +1,18 @@ + + title: "Browsing the Passed" + permalink: '25-browsing-the-passed' + date: {m: 11, d: 26, y: 2017} + categories: + - 'text' + +--- + +# Browsing the Passed + +You can beat a game. Then it's done, but later on, you can explore it again. And yet, you know, the game is *done* - chances are there may not even be a future continuation of it (like a sequel, or a companion game). The series might've long been finished, or abandoned. So.. it sort of gives a weird feeling, to wander the game's world. Like, you're exploring all that it will ever be - an ended, completed world, no more to come. It's strange. + +And that can be applied to.. people. Once someone's died, or taken a totally different, separate name online, they're gone. But what they've written, what they've made - that isn't. So you get the same feeling of exploring a finished, completed existence. + +Another thing. As you explore the finished game, or the creations of a person passed online, you might find new things. Maybe it's a close detail, or an obscure project.. or even just a different way of looking at, of understanding the game or person. So, even though there's nothing that's actually being made new, the game or person sort of still lives on, and has more to say. I guess that's what's so interesting about it. + +A game, a person passed - and yet there's still more to it. The completion of a game or the disappearance of a person; neither really means the end of the story. diff --git a/site/posts/26-old-sketches.md b/site/posts/26-old-sketches.md new file mode 100644 index 0000000..069f238 --- /dev/null +++ b/site/posts/26-old-sketches.md @@ -0,0 +1,19 @@ + + title: "Old sketches" + permalink: '26-old-sketches' + date: {m: 12, d: 2, y: 2017} + thumbnail: 'static/media/26-old1.png' + presentArt: true + categories: + - 'art' + +--- + +# Old sketches + +![Old sketch 1](static/media/26-old1.png) +![Old sketch 2](static/media/26-old2.png) + +I found these old sketches in a folder I hadn't touched for a year or so. I like them, so - here they are! I think I drew these at a friend's house in mid-late-ish 2016; I guess my sketch style hasn't changed all that much since then. + +(One thing I noticed, looking at these pictures again, is that they're almost like pen sketches. I don't think I had an eraser on hand at the time. Maybe I should try more eraser-less sketches.. these have a nice style, but I think they also radiate a bit more emotion than most of my digital or with-eraser drawings!) diff --git a/site/posts/27-face-doodles.md b/site/posts/27-face-doodles.md new file mode 100644 index 0000000..fb4a74f --- /dev/null +++ b/site/posts/27-face-doodles.md @@ -0,0 +1,25 @@ + + title: "Face doodles" + permalink: '27-face-doodles' + date: {m: 1, d: 2, y: 2018} + thumbnail: 'static/media/27-face-doodles-thumb.png' + presentArt: true + categories: + - 'art' + +--- + +# Face doodles + +[![A bunch of face doodles][image]][image] + +Happy new year! + +I got a tablet for Christmas; here's some doodling I did with it. (The image is really big and definitely looks better when you view it at full size; just click the image (or [here][image]) to see it in large.) + +By the way, this was created with FREE<sup>TM</sup> software. I've moved from MediBang to Krita! Wow! MediBang Paint Pro is still a nice painter for sure, but it's not free-as-in-freedom *or* supported on Linux. (Ohh, that also means I don't have to use the family MacBook (nonfree again, aha) for drawing; I can just use my own Debian desktop!) + +I could give you the Krita file for this but there's no real reason to; it's just one layer of the fill-circle brush. (Still [CC BY-SA 4.0][license], [per usual](posts/21-digital-art-again.html), though!) + + [image]: static/media/27-face-doodles.png + [license]: https://creativecommons.org/licenses/by-sa/4.0/ diff --git a/site/posts/28-rainy-sketch.md b/site/posts/28-rainy-sketch.md new file mode 100644 index 0000000..160d781 --- /dev/null +++ b/site/posts/28-rainy-sketch.md @@ -0,0 +1,22 @@ + + title: "Rainy sketch" + permalink: '28-rainy-sketch' + date: {m: 1, d: 12, y: 2018} + thumbnail: 'static/media/28-sketchen.png' + presentArt: true + categories: + - 'art' + +--- + +# Rainy sketch + +![Rainy sketch](static/media/28-sketchen.png) + +I referenced a lot of photos and other artists' drawings to throw together this, but what turned out was rather different from the pictures I used. I like this, though! + +There wasn't really a "hardest part" to this; figuring out the leg shape took a while, but so did the head (front-facing beaks are hard to draw!), and I spent a fair amount of time just thinking about it, doodling the dress and body shape. + +The rain was added on after; I really like it - it makes the whole scene look more real. I've also got a save of the rain-less version. If you're curious, you can see that [here](static/media/28-sketchen-drier.png). + +Also, yay, full body sketches??! I don't do these enough - they really give a lot of interesting practice! diff --git a/site/posts/29-there-was-color.md b/site/posts/29-there-was-color.md new file mode 100644 index 0000000..0850335 --- /dev/null +++ b/site/posts/29-there-was-color.md @@ -0,0 +1,14 @@ + + title: "There was color" + permalink: '29-there-was-color' + date: {m: 1, d: 14, y: 2018} + thumbnail: 'static/media/29-there-was-color.png' + presentArt: true + categories: + - 'art' + +--- + +# There was color + +![There was color](static/media/29-there-was-color.png) diff --git a/site/posts/3-rewrote-it-again.md b/site/posts/3-rewrote-it-again.md new file mode 100644 index 0000000..4180f4b --- /dev/null +++ b/site/posts/3-rewrote-it-again.md @@ -0,0 +1,30 @@ + + title: "Rewrote it Again" + permalink: '3-rewrote-it-again' + date: {m: 4, d: 14, y: 2017} + categories: + - 'dev' + - 'text' + +--- + +# Rewrote it Again + +Tadaaa! It's now all-shiny and new. + +If I'm lucky you won't notice much of a difference in the actual site. It's +got the same stylesheet it used to have. All of the webpage rendering is done +on my computer before the site is published to GitHub Pages - *not* by your +web browser. That means the site should work on practically any device, +regardless of JavaScript support (though, of course, Disqus still won't work +on no-script browsers). + +There *is* a better archive section, though. It's now split into multiple +categories, and those (probably) function fine, so that's user-visible +progress! + +I'd like to seriously post some new art here, but I haven't drawn much +recently! ..haha, that's a fun excuse. I did work a bit on practicing some +lighting techniques but it didn't turn out as well as I was hoping it to (duh). +I guess it was still worth doing, though, since practice, and also because I +figured out a better way of practicing. diff --git a/site/posts/30-art-dump.md b/site/posts/30-art-dump.md new file mode 100644 index 0000000..25aa3f7 --- /dev/null +++ b/site/posts/30-art-dump.md @@ -0,0 +1,46 @@ + + title: "Art dump" + permalink: '30-art-dump' + date: {m: 1, d: 26, y: 2018} + categories: + - 'art' + +--- + +# Art dump + +January! I drew a few things. Besides what I've already posted in the [art](archive/art.html) category, here's a quick post with most of the things I made. + +Every image can be clicked to view a full-resolution copy of the picture, and I've included the Krita files for each as well. As usual, all this is licensed under [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/). + +[![Forest color test](static/media/30-forest-color-test.png)](static/media/30-forest-color-test.png) +([forest-color-test.kra](static/media/30-forest-color-test.kra)) + +[![Tubes](static/media/30-tubes.png)](static/media/30-tubes.png) +([tubes.kra](static/media/30-tubes.kra)) + +[![Further doodles](static/media/30-further-doodles.png)](static/media/30-further-doodles.png) +([further-doodles.kra](static/media/30-further-doodles.kra)) + +[![Dragon thingy](static/media/30-dragon-thingy.png)](static/media/30-dragon-thingy.png) +([dragon-thingy.kra](static/media/30-dragon-thingy.kra)) + +[![Full test](static/media/30-full-test.png)](static/media/30-full-test.png) +([full-test.kra](static/media/30-full-test.kra)) + +[![Textured brush doodles](static/media/30-textured-brush-doodles.png)](static/media/30-textured-brush-doodles.png) +([textured-brush-doodles.kra](static/media/30-textured-brush-doodles.kra)) + +[![Scenery sketch](static/media/30-scenery-sketch.png)](static/media/30-scenery-sketch.png) + +([scenery-sketch.kra](static/media/30-scenery-sketch.kra)) + +[![Color dino thing](static/media/30-color-dino-thing.png)](static/media/30-color-dino-thing.png) +([color-dino-thing.kra](static/media/30-color-dino-thing.kra)) + +[![Face structure thing](static/media/30-face-structure-thing.png)](static/media/30-face-structure-thing.png) +([face-structure-thing.kra](static/media/30-face-structure-thing.kra)) + +Wow! That's a fair bit. Tablets are handy for throwing things together quickly. + +Sorry for not making individual posts for each of these; I think it's actually easier for me to draw something when I know I'm not going to have to write an entire article on it after, hah. There's not a ton to say about most of these. They're just doodles! But I'm glad to share them anyways. diff --git a/site/posts/31-art-dump-feb.md b/site/posts/31-art-dump-feb.md new file mode 100644 index 0000000..a9b3e25 --- /dev/null +++ b/site/posts/31-art-dump-feb.md @@ -0,0 +1,58 @@ + + title: "Art dump February!!" + permalink: '31-art-dump-feb' + date: {m: 3, d: 2, y: 2018} + categories: + - 'art' + +--- + +# Art dump February!! + +February!!!!!!! Wow! Did you know February only has 28 days in it, usually? That means that the 30th of February, today, does not actually exist. Oops. Well, the end of the month snuck up on me. + +Here's most of my art from the month! There are a LOT of doodle pages, which I've put past the non-doodles. + +<art>31-whoatree</art> + +<art>31-colorrrrs</art> + +<art>31-proftest</art> + +<art>31-aaaAAAAAAAAAAAw</art> + +<art>31-Dooold!!!</art> + +<art>31-DloLold</art> + +<art>31-Biiiig and things</art> + +<art>31-Stuffssss</art> + +<art>31-a bunch of things!!!!</art> + +<art>31-dumpdumpdumpdumpdumpBSTRACT!!!!!!!</art> + +<art>31-Doodododododododo</art> + +<art>31-Dragon? Things???</art> + +<art>31-dodddles</art> + +<art>31-uhhhhbird?</art> + +<art>31-Spooky??????</art> + +<art>31-Sit</art> + +<art>31-sketches and things!!</art> + +<art>31-More doodles!!</art> + +<art>31-Pink abstract stuff</art> + +<art>31-Orange abstract stuff</art> + +<art>31-Green abstract stuff</art> + +SORRY MOBILE DATA diff --git a/site/posts/32-domain-transfer-plus-mastodon.md b/site/posts/32-domain-transfer-plus-mastodon.md new file mode 100644 index 0000000..63579e4 --- /dev/null +++ b/site/posts/32-domain-transfer-plus-mastodon.md @@ -0,0 +1,38 @@ + + title: "Domain transfer?!?! + Mastodon" + permalink: '32-domain-transfer-plus-mastodon' + date: {m: 3, d: 23, y: 2018} + categories: + - 'dev' + - 'text' + +--- + +# Domain transfer?!?! + Mastodon + +Courtesy of super-awesome [eq][eq], my site is now hosted on florrie.[ed1.club][ed1club] instead of towerofnix.github.io! Yay!!! <code>SPARKLES EMOJI</code> <code>SPARKLES EMOJI</code> <code>SPARKLES EMOJI</code>! + +I'll eventually get around to updating all the links to towerofnix.github.io/blog, but I already have a redirection system in place to make the transition a lot smoother. + +--- + +I've somehow managed to not get around to mentioning it on my blog, but..... + +I've got a Mastodon account! It's [florrie@cybre.space][flcybre]. Cybre.space is supre cool. (I did *not* make that typo on purpose.) I post my art there before I put it up here on my blog, for a few reasons: + +* Putting together and publishing a blog post is kind of difficult. I've written my blog system to be as pain-free as possible, but it's just not as easy as typing a toot and hitting send. Keeping it simple to share my art means I get to focus on what I'm drawing instead of the process of sticking it online, since... + +* I still put my art on this blog, but only once every month or so. (You've noticed my art dumps, right?) Posting it in batches does keep my archive a bit cleaner, which is nice. + +* Posting art in batches lets me refine art when I want to. I made a doodle earlier this month and changed some things about it since; if I'd already posted the art on my blog, I would have had to update the krita and PNG files, and then go through the entire process of pushing my site again. + +* My Mastodon account's toots are set to private by default: only my followers can see them (and I have to manually approve follow requests). This makes it a lot easier to share things online - when I draw my art, I don't have to worry about what the entire world is going to think of it. It's easy for me to just decide to keep the piece exclusive to my hand-selected followers on Mastodon. + +The tl;dr is: **If you follow [me on Mastodon][flcybre] (and I accept your follow request), you will get access to my art earlier than when I post it on my blog.** + +Also, eq helped me get some directory listings set up here. If you go to [florrie.ed1.club/stuff][stuff], you may or may not find random junk I throw there; I usually just use cubeupload, so it probably won't be populated all that quickly. I've also got a /private folder for the Krita files of things I share on Mastodon but don't plan on posting to my blog. (Obviously, that directory is password-protected. If you want access, and I've accepted your follow request on Mastodon, I can probably add an entry for you in my htpasswd file. Or something. I don't 100% know how I'm going to use the folder yet!) + + [eq]: https://eq.ed1.club + [ed1club]: https://ed1.club + [flcybre]: https://cybre.space/@florrie + [stuff]: https://florrie.ed1.club/stuff diff --git a/site/posts/33-art-dump-mar.md b/site/posts/33-art-dump-mar.md new file mode 100644 index 0000000..3db6cd8 --- /dev/null +++ b/site/posts/33-art-dump-mar.md @@ -0,0 +1,34 @@ + + title: "Art dump March!!!" + permalink: '33-art-dump-mar' + date: {m: 4, d: 1, y: 2018} + categories: + - 'art' + +--- + +# Art dump March!!! + +March! Quite a few things. Less doodles, but more relatively-complete things. Actually, maybe "less doodles" isn't right - rather, there's more doodles that I actually went further with! ...Okay, less doodles than February anyways :) + +Clicking any image will view it directly, so you can zoom in or view it in fullscreen. As usual, these are all [CC BY-SA 4.0][ccbysa] licensed. + +<art>33-Honey</art> + +<art>33-Eyemark</art> + +<art>33-HmMmm</art> + +<art>33-Bannnaaar</art> + +<art>33-Sheep Doodle</art> + +<art>33-Been a whiiiile</art> + +<art>33-tiny abbbstrkact</art> + +<art>33-Doodlies</art> + +<art>33-Doodly Thing</art> + + [ccbysa]: https://creativecommons.org/licenses/by-sa/4.0/ diff --git a/site/posts/34-art-dump-april.md b/site/posts/34-art-dump-april.md new file mode 100644 index 0000000..b27bdec --- /dev/null +++ b/site/posts/34-art-dump-april.md @@ -0,0 +1,30 @@ + + title: "Art dump April!!!!" + permalink: '34-art-dump-april' + date: {m: 5, d: 1, y: 2018} + categories: + - 'art' + +--- + +# Art dump April!!!! + +April! A decent bit of stuff, although I've been having a couple weeks of Not Drawing Much - most of this is from the first half of the month. I've put a relatively more-significant-than-normal time into a bunch of these (and less in others!). + +<art>34-Cotton Candy Chimaera</art> + +<art>34-Fastchar</art> + +<art>34-aaaaaPerspectave</art> + +<art>34-persp1</art> + +<art>34-Toooongue</art> + +<art>34-Siit</art> + +<art>34-Aquatic</art> + +<art>34-Abstract Purple</art> + +<art>34-Tree</art> diff --git a/site/posts/35-art-dump-may.md b/site/posts/35-art-dump-may.md new file mode 100644 index 0000000..a4901e7 --- /dev/null +++ b/site/posts/35-art-dump-may.md @@ -0,0 +1,38 @@ + + title: "Art dump May!!!!!" + permalink: '35-art-dump-may' + date: {m: 6, d: 1, y: 2018} + categories: + - 'art' + +--- + +# Art dump May!!!!! + +Maaay!!!!!!!!!! Whee. A few new things! + +<art>35-Sleeeeeeep</art> + +<art>35-nocrashpls</art> + +<art>35-choc</art> + +<art>35-dangit</art> + +<art>35-Fiiiire</art> + +<art>35-gh</art> + +<art>35-pixeljunkeqen</art> + +This one just above is based on [a thing eq made!](https://eq.ed1.club/art/2018-05-06-.html) + +<art>35-spin</art> + +<art>35-creepyabstract</art> + +<art>35-brushtest</art> + +<art>35-dlothes</art> + +<art>35-drlod</art> diff --git a/site/posts/4-wrote-a-thing.md b/site/posts/4-wrote-a-thing.md new file mode 100644 index 0000000..0e5201d --- /dev/null +++ b/site/posts/4-wrote-a-thing.md @@ -0,0 +1,25 @@ + + title: "I Wrote a Thing" + permalink: '4-wrote-a-thing' + date: {m: 4, d: 18, y: 2017} + categories: + - 'text' + +--- + +# I Wrote a Thing + +I wrote a thing. Just kidding! I wrote an *idea* for a thing. Eventually it'll +hopefully turn into a real story, but I've got absolutely zero experience with +writing stories, and even less dedicating to relatively big projects. + +I'd like to do some concept art for the project. In my head I imagined it as a +super fancy art project, but that's kind of a crazy idea, since the thing's so +(relatively) big. So I'll most likely write it as a proper story first. Maybe I +could try having pictures appear inside the page, so that there's still art to +look at (in a printed version of it, anyways[\*]), and I get to have somewhere +to present the art (other than here). + +(\* I'm *not* making this a proper printed sold in stores thing, haha. It's +just a thing that's stemmed from a school assignment. I'll probably print it +for myself. That is, if I go anywhere with it.) diff --git a/site/posts/5-better-art.md b/site/posts/5-better-art.md new file mode 100644 index 0000000..346ce3e --- /dev/null +++ b/site/posts/5-better-art.md @@ -0,0 +1,52 @@ + + title: "Better Art" + permalink: '5-better-art' + date: {m: 4, d: 20, y: 2017} + thumbnail: 'static/media/05-character.png' + presentArt: true + categories: + - 'art' + - 'text' + +--- + +# Better Art + +![A new character](static/media/05-character.png) + +I drew this character earlier. They're not really of a specific species, nor do +they have a story (or name..), but it's an interesting art piece (to me) for +several reasons.. + +* **It's the first drawing I made using a sketch.** That said, I'm not entirely + sure what counts as a sketch, but I *think* [this][sketch] does? I drew the + sketch using a single basic wet brush at a fixed size, then later (the next + day) drew over (a transparent version of) it with more detail and such. + +* **The body proportions make (more) sense.** I've drawn very few full-body + humanoids, but the general body size seems to be a bit better on this. Maybe + their head is a bit big..? But I think I found that making the head much + smaller just didn't look as good. + +* **It's a relatively detailed drawing.** I tried a few things with this + picture that mostly worked well, I think. Most obvious is the neck fur, + which I definitely *didn't* steal from probably every drawing of this type + of creature, ever. Their head also looks pretty fluffy, which is definitely + what I was going for. There's also some subtle details I probably could have + made a bit more visible - some lighting experimentation, inner-ear coloring, + and the whole shadow-above-nose effect. + +So it's a better piece than most of my previous stuff. Progress feels good? +(Then again, I still don't really do *enough* practice! This was the first +picture I'd drawn in just over a week.) + +I also worked a little bit on the story idea I [had][story]. I wrote out a bit +of plot for a new character (no, this isn't a picture of her). I'm kind of +wondering how many tropes or clichés it's fitting in to, though? That's +probably something every writer gets at some point, but I'm going to try not to +let it get too much in the way of me writing. Practice makes perfect, so I +can't really let myself get discouraged by focusing too much on all the common +story issues I'm falling into! + + [sketch]: static/media/05-sketch.png + [story]: posts/4-wrote-a-thing.html diff --git a/site/posts/6-this-drawing-weighs-a-lot.md b/site/posts/6-this-drawing-weighs-a-lot.md new file mode 100644 index 0000000..8547073 --- /dev/null +++ b/site/posts/6-this-drawing-weighs-a-lot.md @@ -0,0 +1,22 @@ + + title: "This drawing weighs a lot" + permalink: '6-this-drawing-weighs-a-lot' + date: {m: 4, d: 22, y: 2017} + thumbnail: 'static/media/06-yikes.png' + presentArt: true + categories: + - 'art' + +--- + +# This drawing weighs a lot + +![Yikes](static/media/06-yikes.png) + +I'm a bit sick, but I guess I drew something anyways. It's obviously not a very +good picture. + +It's actually not the art piece with the biggest file size ([.mdp][medibang] +source, anyways; I'm not sure how large the biggest source-file's PNG is). + + [medibang]: https://medibangpaint.com/ diff --git a/site/posts/7-quick-update.md b/site/posts/7-quick-update.md new file mode 100644 index 0000000..fec3cea --- /dev/null +++ b/site/posts/7-quick-update.md @@ -0,0 +1,87 @@ + + title: "Quick update" + permalink: '7-quick-update' + date: {m: 5, d: 6, y: 2017} + thumbnail: 'static/media/07-cayenne-colored.png' + presentArt: true + categories: + - 'art' + - 'text' + +--- + +# Quick update + +So I know it looks like I've abandoned the blog. As it would turn out, I +haven't! –– + +!["abandon": cease to support or look after (someone); desert.](static/media/07-abandon.png) + +Okay, I *guess* I've abandoned the blog a little. But the second definition +there certainly hasn't happened: + +!["abandon": give up completely (a course of action, a practice, or a way of thinking).](static/media/07-abandon-2.png) + +I've actually been looking for things to write about. I've had a little bit of +trouble since I mostly haven't made very many things in the last couple weeks +(wait, *three* weeks..? Sorry - really.), but I've made a few things now, so I +can share those. + +## A Discord game + +I wrote a bit of code for a Discord multi-user-dungeon/domain/what*ever*. It's +almost definitely not going to go anywhere, but it's the first time I've really +written any code in the last week or so, and I'd rathre not get rusty. + +The test server isn't public right now, since that's all it is - a bot testing +server. It's also kind of a collaboration with a friend of mine[\*]; we haven't +really figured out any of the story. At all. At *all.* We also have basically +no experience with games like this, either (or really any multiplayer games), +so this is really just a programming project for me, haha. + +(\* His online username changes every half-week or so, but right now it's +[lightblub][lightblub]. If you look for the user "alex" in +[this GitHub organization][coolandgood] that he and I are a part of, you can probably find him. Hey, I'm not giving you instructions on how to stalk his +every move, just a means of seeing all the cool projects he's worked on! +`SMILEY_FACE.`) + +## Some art + +I did a little bit of art? Not near as much as I wanted to, though. + +![SPOILERS!!!! it's a bowl of vegetables and/or vegetables.](static/media/07-my-art-is-deteriorating.png) + +This drawing's supposedly supposed to be a picture of a bowl of fruits and +vegetables and whatnot. Everybody else was drawing things without using any +line art at all, so, I guess I tried to do something, too? It's not +particularly good though! + +## [The story](posts/4-wrote-a-thing.html) + +I didn't work on the story. + +I know - disappointing. I still *like* the idea for the story, but I didn't go +far with it. That's kind of been how most of my stories go - and many of my +non-writing related projects too, I guess. Maybe I'll pick up with the idea +again eventually, since writing stories still sounds like a really cool idea to +me, but I haven't really felt like it lately. The trouble is that I haven't +gotten any ideas, any spurts of, "oh, *this* would work" - but that kind of +stems from the fact that I didn't really bother thinking about it much. So! +Again, maybe I'll work on it again eventually. When I feel like writing, I'll +have a (bare beginning of a) story to go to, so, who knows? I'll probably see +more of it, eventually. + +## So. + +Such ends the Quick(tm) update. + +Since I know there's thousands of you readers dying to comment on my posts, +it really doesn't take any effort - you can sign up for Disqus right through +the commenting form at the bottom of all post pages, and that's not even +*necessary*, since you can also use Google or Twitter just like that! +(Or, ugh, Facebook.) + +(You lost the game. Okay, bye!) + + [lightblub]: https://github.com/lightblub + [coolandgood]: https://github.com/orgs/coolandgood/people diff --git a/site/posts/8-a-snake-maybe.md b/site/posts/8-a-snake-maybe.md new file mode 100644 index 0000000..64ca3a3 --- /dev/null +++ b/site/posts/8-a-snake-maybe.md @@ -0,0 +1,22 @@ + + title: "A snake, maybe?" + permalink: '8-a-snake-maybe' + date: {m: 5, d: 13, y: 2017} + thumbnail: 'static/media/08-a-snake-maybe.png' + presentArt: true + categories: + - 'art' + +--- + +# A snake, maybe? + +![A snake-like thing](static/media/08-a-snake-maybe.png) + +I drew a little thing. This spawned from me drawing random shapes on a canvas +while being uninterested in drawing anything late last night, then accidentally +painting [an idea-spawning shape](static/media/08-idea.png). + +This is the first post I've published on an odd-numbered day! That's a little +bit frustrating, but I guess it isn't really worth stressing over, since I +don't even have a when-to-post schedule. diff --git a/site/posts/9-blog-images-on-gh-and-the-base-tag.md b/site/posts/9-blog-images-on-gh-and-the-base-tag.md new file mode 100644 index 0000000..9947cf3 --- /dev/null +++ b/site/posts/9-blog-images-on-gh-and-the-base-tag.md @@ -0,0 +1,59 @@ + title: "Being sad about how images don't work on the blog's GitHub page (AKA: An explanation of the 'base' HTML tag)" + permalink: '9-blog-images-on-gh-and-the-base-tag' + date: {m: 5, d: 17, y: 2017} + categories: + - 'dev' + - 'text' + +--- + +<!-- I'm lazy and don't feel like changing this all into following the "no more than 80 characters on a line" rule; I originally wrote this as a GItHub issue. Sorry! --> + +# Being sad about how images don't work on the blog's GitHub page +## (AKA: An explanation of the `<base>` HTML tag) + +Case in point: [this.](https://github.com/towerofnix/blog/blob/38a7b7e0a52e92474ce215b5eae87cf30cf9becd/posts/8-a-snake-maybe.md) + +The issue is that I cheat<sup>[1]</sup> and use the [`<base>`][base-tag] tag to have easier embed URLs for things like images. + +For example, suppose I write `my-fancy-post.md`, and I want to include the image `cool-image.png`. My options: + +1. The easy way. Use absolute paths: `![Cool image!](/static/media/cool-image.png)`. But what if the blog's root path isn't `/`? In fact, it *isn't* – my blog's currently presented at `https://towerofnix.github.io/blog/`. We'd be embedding `https://towerofnix.github.io/static/media/cool-image.png`. And that doesn't exist, of course; I don't have a `static` folder under [`liam4.github.io.git`](https://github.com/towerofnix/towerofnix.github.io) or [a repository with that name](https://github.com/towerofnix/static/), so GitHub pages considers it to not exist. What we want is <code>https:/<b></b>/liam4.github.io<strong>/blog</strong>/static/cool-image.png</code>. + +2. The (just slightly) less easy way. Use relative paths: `![Cool image!](../static/media/cool-image.png)`. *That* should work, right? The post HTML documents are stored at `(site root)/posts/blah.html`, so we'd be referencing `(site root)/posts/../static/media/cool-image.png`, or, simplified, `(site root)/static/media/cool-image.png`. Great! It works. Except – hold on. We display the most recent post [on the homepage](https://github.com/towerofnix/blog/issues/1), at `(site root)/index.html`. That means, from `index.html`, we'll be loading `(site root)/../static/media/cool-image.png`. And that's not right.<sup>[2]</sup> (citation: option 1). + +3. The right way (which takes too long to explain in a single list item). Use [`<base>`][base-tag]: `<base href='https://liam4.github.io/blog/'> (..) ![Cool image!](static/media/cool-image.png)`. + +`<base>` seems like magic at first, but it isn't really [black magic](http://www.catb.org/jargon/html/B/black-magic.html), because it's in the spec.<sup>[3]</sup> Basically<sup>[4]</sup>, `<base>` changes where relative paths are relative to. So by changing it to `https://liam4.github.io/blog/`, all relative paths are loaded according to that. `foo/bar.html` will *always* refer to `https://liam4.github.io/blog/foo/bar.html`, as long as there's a `<base>` tag that href's to `https://liam4.github.io` in the document. + +Bonus points for using `<base>`: + +* It's really easy to embed `<base>` on every page, just by having it be in the `<head>`-part of my generic blog page template. + +* `<base>` also works on things like links, which makes it *really* easy to link to any page on the blog, from any page on the blog. (Example: the navigation bar has the same HTML on every page of the blog, but the links still work irregardless of what page the navigation bar actually shows up on. Another: linking to a blog post from anywhere on the site – even other post pages – is easy; just link to `posts/post-file.html`!) + +The only problem with `<base>` is that when you're viewing an HTML preview of your markdown file, you don't get to see the images, since the `<base>` is ignored while the HTML preview is compiled.<sup>[5]</sup> (It should be pretty clear why – `![](static/media/foo.png` no longer refers to `https://liam4.github.io/static/media/foo.png`, but rather, `(current directory)/static/media/foo.png`, which won't appear correctly unless `(current directory)` happens to be the site root (i.e. the directory containing `static`).) + +I don't really see a way to solve that, though. The benefits greatly outweigh that cost, so `<base>` is what I'm using. + +--- + +PS, did anybody here by chance know me by my art and not my code? + +..Nah, I'm the only person who follows my blog anyways. And the only other people that do probably know me from (generally) code-writing-based communities, for the stuff I've programmed, not my art. + +<!-- PPS, to anybody reading the source code of this post, I've thought about how ending my post with "also, you lost the game" might actually make people comment *less*, since they'll be required to go tell somebody they lost the game (as per The Rules), which is essentially a distraction. Also, you lost the game! --> + +--- + +<sup>[1] This isn't actually true; I'm pretty sure I'm following the intended use of `<base>`. See option 3.</sup> + +<sup>[2] See option 1.</sup> + +<sup>[3] "It's in the spec" doesn't really make something *not* [magic](http://www.catb.org/jargon/html/M/magic.html), if nobody can understand what the spec is saying, which is sometimes the case (citation needed). What makes it not magic is that it actually does make sense when I explain it to you (citation needed).</sup> + +<sup>[4] I get to say that because nobody else says it (since nobody knows about `<base>`). (Cheesy "all right I'll leave now" here.)</sup> + +<sup>[5] [Markdown is *supposed* to let you embed any HTML tags you want into your document](https://daringfireball.net/projects/markdown/syntax#html), but GitHub doesn't follow that rule and gets rid of `<base>` tags (and *every* tag except for what's on [their whitelist](https://github.com/jch/html-pipeline/blob/1b5058918eeb0507ac225934cd3e9238f0b94139/lib/html/pipeline/sanitization_filter.rb#L42-L49)) when it compiles markdown previews. So, [sure enough](https://gist.github.com/towerofnix/ac3a23d5fefbde422f44685674f5feac), setting the `<base>` within the markdown document doesn't work for GitHub markdown previews, which is the use case in which our issue practically lies.</sup> + + [base-tag]: https://developer.mozilla.org/en/docs/Web/HTML/Element/base diff --git a/site/static/site.css b/site/static/site.css new file mode 100644 index 0000000..9483ed9 --- /dev/null +++ b/site/static/site.css @@ -0,0 +1,114 @@ +@import url('https://fonts.googleapis.com/css?family=Merriweather:400,700|Martel:700|Ubuntu+Mono'); +@import url('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1/katex.min.css'); + +body { + background-color: hsl(60, 70%, 95%); + color: #333; + font-family: Merriweather; + font-size: 0.8em; + line-height: 1.4; +} + +#nav { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + padding-bottom: 4px; +} + +.top-block { + background-color: hsl(60, 50%, 97%); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); + max-width: 800px; + margin-left: auto; + margin-right: auto; +} + +#main { + padding: 30px; +} + +footer { + margin-top: 20px; + margin-bottom: 20px; + padding: 10px 30px; + text-align: center; +} + +/* Text formatting, etc */ + +a { + color: #777; +} + +#main a:visited { + color: #555; +} + +b { + font-weight: 700; +} + +img { + max-width: 100%; +} + +h1, h2, h3, h4, h5, h6 { + font-family: Martel; +} + +h1 { + font-size: 2.4em; + margin-bottom: 0.3em; + font-weight: 700; +} + +h2 { + font-size: 1.8em; + margin-bottom: 0.2em; +} + +code { + font-family: 'Ubuntu Mono'; + background-color: hsl(60, 75%, 92%); + padding: 0.1em 0.25em; +} + +blockquote { + color: #999; + font-style: italic; +} + +.inline-math img { + vertical-align: middle; +} + +.inline-math { + break-inside: avoid; +} + +/* Tables */ + +table { + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid #CCC; + width: 100%; + border-collapse: collapse; +} + +th, td { + padding: 2px; +} + +/* Page specific stuff ----------------------------------------------------- */ + +/* Post pages */ + +.post-meta { + font-style: italic; + color: #AAA; +} + +/* Archive pages */ + +.date-col { + width: 150px; +} |