push my changes to new repo

This commit is contained in:
N-Upchurch 2023-07-05 11:06:31 -05:00
parent 4f0a64d781
commit 5e0eb0cdcf
118 changed files with 1696 additions and 358 deletions

9
.kateproject.notes Normal file
View File

@ -0,0 +1,9 @@
TODO:
Handle main post image and in-article images
Integrate est. read time
Integrate % done in article
Investigate pagination for /blog/
Add Favicon
Integrate recipe structured data for recipe type articles
Consider optimising character count per line

121
README.md
View File

@ -1,123 +1,6 @@
# eleventy-base-blog v8
A starter repository showing how to build a blog with the [Eleventy](https://www.11ty.dev/) site generator (using the [v2.0 release](https://www.11ty.dev/blog/eleventy-v2/)).
[![Netlify Status](https://api.netlify.com/api/v1/badges/802669dd-d5f8-4d49-963d-6d57b257c2a2/deploy-status)](https://app.netlify.com/sites/eleventy-base-blog/deploys)
## Getting Started
* [Want a more generic/detailed getting started guide?](https://www.11ty.dev/docs/getting-started/)
1. Make a directory and navigate to it:
```
mkdir my-blog-name
cd my-blog-name
```
2. Clone this Repository
```
git clone https://github.com/11ty/eleventy-base-blog.git .
```
_Optional:_ Review `eleventy.config.js` and `_data/metadata.js` to configure the sites options and data.
3. Install dependencies
```
npm install
```
4. Run Eleventy
Generate a production-ready build to the `_site` folder:
```
npx @11ty/eleventy
```
Or build and host on a local development server:
```
npx @11ty/eleventy --serve
```
Or you can run [debug mode](https://www.11ty.dev/docs/debugging/) to see all the internals.
## Features
- Using [Eleventy v2.0](https://www.11ty.dev/blog/eleventy-v2/) with zero-JavaScript output.
- Content is exclusively pre-rendered (this is a static site).
- Can easily [deploy to a subfolder without changing any content](https://www.11ty.dev/docs/plugins/html-base/)
- All URLs are decoupled from the contents location on the file system.
- Configure templates via the [Eleventy Data Cascade](https://www.11ty.dev/docs/data-cascade/)
- **Performance focused**: four-hundos Lighthouse score out of the box!
- [View the Lighthouse report for the latest build](https://eleventy-base-blog.netlify.app/reports/lighthouse/) courtesy of the [Netlify Lighthouse plugin](https://github.com/netlify/netlify-plugin-lighthouse).
- _0 Cumulative Layout Shift_
- _0ms Total Blocking Time_
- Local development live reload provided by [Eleventy Dev Server](https://www.11ty.dev/docs/dev-server/).
- Content-driven [navigation menu](https://www.11ty.dev/docs/plugins/navigation/)
- [Image optimization](https://www.11ty.dev/docs/plugins/image/) via the `{% image %}` shortcode.
- Zero-JavaScript output.
- Support for modern image formats automatically (e.g. AVIF and WebP)
- Prefers `<img>` markup if possible (single image format) but switches automatically to `<picture>` for multiple image formats.
- Automated `<picture>` syntax markup with `srcset` and optional `sizes`
- Includes `width`/`height` attributes to avoid [content layout shift](https://web.dev/cls/).
- Includes `loading="lazy"` for native lazy loading without JavaScript.
- Includes [`decoding="async"`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decoding)
- Images can be co-located with blog post files.
- View the [Image plugin source code](https://github.com/11ty/eleventy-base-blog/blob/main/eleventy.config.images.js)
- Per page CSS bundles [via `eleventy-plugin-bundle`](https://github.com/11ty/eleventy-plugin-bundle).
- Built-in [syntax highlighter](https://www.11ty.dev/docs/plugins/syntaxhighlight/) (zero-JavaScript output).
- Blog Posts
- Draft posts: use `draft: true` to mark a blog post as a draft. Drafts are **only** included during `--serve`/`--watch` and are excluded from full builds. View the [Drafts plugin source code](https://github.com/11ty/eleventy-base-blog/blob/main/eleventy.config.drafts.js).
- Automated next/previous links
- Accessible deep links to headings
- Generated Pages
- Home, Archive, and About pages.
- [Feeds for Atom and JSON](https://www.11ty.dev/docs/plugins/rss/)
- `sitemap.xml`
- Zero-maintenance tag pages ([View on the Demo](https://eleventy-base-blog.netlify.app/tags/))
- Content not found (404) page
## Demos
- [Netlify](https://eleventy-base-blog.netlify.com/)
- [GitHub Pages](https://11ty.github.io/eleventy-base-blog/)
- [Remix on Glitch](https://glitch.com/~11ty-eleventy-base-blog)
## Deploy this to your own site
Deploy this Eleventy site in just a few clicks on these services:
- [Get your own Eleventy web site on Netlify](https://app.netlify.com/start/deploy?repository=https://github.com/11ty/eleventy-base-blog)
- If you run Eleventy locally you can drag your `_site` folder to [`drop.netlify.com`](https://drop.netlify.com/) to upload it without using `git`.
- [Get your own Eleventy web site on Vercel](https://vercel.com/import/project?template=11ty%2Feleventy-base-blog)
- Read more about [Deploying an Eleventy project](https://www.11ty.dev/docs/deployment/) to the web.
# nathanupchurch.com - Based on eleventy-base-blog v8
### Implementation Notes
- `content/about/index.md` is an example of a content page.
- `content/blog/` has the blog posts but really they can live in any directory. They need only the `posts` tag to be included in the blog posts [collection](https://www.11ty.dev/docs/collections/).
- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`.
- Content can be in _any template format_ (blog posts neednt exclusively be markdown, for example). Configure your projects supported templates in `eleventy.config.js` -> `templateFormats`.
- The `public` folder in your input directory will be copied to the output folder (via `addPassthroughCopy` in the `eleventy.config.js` file). This means `./public/css/*` will live at `./_site/css/*` after your build completes.
- Provides two content feeds:
- `content/feed/feed.njk`
- `content/feed/json.njk`
- This project uses three [Eleventy Layouts](https://www.11ty.dev/docs/layouts/):
- `_includes/layouts/base.njk`: the top level HTML structure
- `_includes/layouts/home.njk`: the home page template (wrapped into `base.njk`)
- `_includes/layouts/post.njk`: the blog post template (wrapped into `base.njk`)
- `_includes/postslist.njk` is a Nunjucks include and is a reusable component used to display a list of all the posts. `content/index.njk` has an example of how to use it.
If your site enforces a Content Security Policy (as public-facing sites should), either, in `base.njk`, disable
```html
<style>{% getBundle "css" %}</style>
```
and enable
```html
<link rel="stylesheet" href="{% getBundleFileUrl "css" %}">
```
or configure the server with the CSP directive `style-src: 'unsafe-inline'` (which is less secure).
- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`.

26
README.md.backup Normal file
View File

@ -0,0 +1,26 @@
# nathanupchurch.com - Based on eleventy-base-blog v8
### Implementation Notes
- `content/blog/` has the blog posts but really they can live in any directory. They need only the `posts` tag to be included in the blog posts [collection](https://www.11ty.dev/docs/collections/).
- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`.
- Content can be in _any template format_ (blog posts neednt exclusively be markdown, for example). Configure your projects supported templates in `eleventy.config.js` -> `templateFormats`.
- The `public` folder in your input directory will be copied to the output folder (via `addPassthroughCopy` in the `eleventy.config.js` file). This means `./public/css/*` will live at `./_site/css/*` after your build completes.
- Provides two content feeds:
- `content/feed/feed.njk`
- `content/feed/json.njk`
- This project uses three [Eleventy Layouts](https://www.11ty.dev/docs/layouts/):
- `_includes/layouts/base.njk`: the top level HTML structure
- `_includes/layouts/home.njk`: the home page template (wrapped into `base.njk`)
- `_includes/layouts/post.njk`: the blog post template (wrapped into `base.njk`)
- `_includes/postslist.njk` is a Nunjucks include and is a reusable component used to display a list of all the posts. `content/index.njk` has an example of how to use it.
If your site enforces a Content Security Policy (as public-facing sites should), either, in `base.njk`, disable
```html
<style>{% getBundle "css" %}</style>
```
and enable
```html
<link rel="stylesheet" href="{% getBundleFileUrl "css" %}">
```
or configure the server with the CSP directive `style-src: 'unsafe-inline'` (which is less secure).

View File

@ -1,11 +1,14 @@
module.exports = {
title: "Eleventy Base Blog v8",
url: "https://example.com/",
title: "Nathan Upchurch",
logo: "/img/logo_favicon.svg",
url: "https://nathanupchurch.com/",
language: "en",
description: "I am writing about my experiences as a naval navel-gazer.",
description: "The personal website and blog of Nathan Upchurch.",
author: {
name: "Your Name Here",
name: "Nathan Upchurch",
email: "youremailaddress@example.com",
url: "https://example.com/about-me/"
}
},
defaultPostImageURL: "/img/default_post_image/vasilina-sirotina-1NMPvajSt9Q-unsplash_copy.avif",
defaultPostImageAlt: "The default post image: a close picture of the dark green leaves of a plant."
}

View File

@ -0,0 +1,8 @@
---
layout: layouts/base.njk
showPostListHeader: yep
---
<h1>404</h1>
<p class="nodropcap page-block">Sorry, it looks like that link is broken. <a href="/">Go to the home page</a>.</p>
{{ content | safe }}

View File

@ -4,48 +4,56 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title or metadata.title }}</title>
<link rel="icon" type="image/x-icon" href="/img/logo_favicon.svg">
<meta name="description" content="{{ description or metadata.description }}">
{#- Atom and JSON feeds included by default #}
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">
<link rel="alternate" href="/feed/feed.json" type="application/json" title="{{ metadata.title }}">
{#- Uncomment this if youd like folks to know that you used Eleventy to build your site! #}
{#- <meta name="generator" content="{{ eleventy.generator }}"> #}
<meta name="generator" content="{{ eleventy.generator }}">
{#-
CSS bundles are provided via the `eleventy-plugin-bundle` plugin:
1. You can add to them using `{% css %}`
2. You can get from them using `{% getBundle "css" %}` or `{% getBundleFileUrl "css" %}`
3. You can do the same for JS: {% js %}{% endjs %} and <script>{% getBundle "js" %}</script>
4. Learn more: https://github.com/11ty/eleventy-plugin-bundle
#}
{#- Add an arbitrary string to the bundle #}
{%- css %}* { box-sizing: border-box; }{% endcss %}
{#- Add the contents of a file to the bundle #}
{#- Bundle CSS #}
{%- css %}{% include "public/css/index.css" %}{% endcss %}
{#- Or add from node_modules #}
{# {%- css %}{% include "node_modules/prismjs/themes/prism-okaidia.css" %}{% endcss %} #}
{#- Render the CSS bundle using Inlined CSS (for the fastest site performance in production) #}
{%- css %}{% include "public/css/webfonts/webfonts.css" %}{% endcss %}
{%- css %}{% include "public/css/dropcap.css" %}{% endcss %}
<style>{% getBundle "css" %}</style>
{#- Renders the CSS bundle using a separate file, if you can't set CSP directive style-src: 'unsafe-inline' #}
{#- <link rel="stylesheet" href="{% getBundleFileUrl "css" %}"> #}
{% if title %}
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Article",
"headline": "{{ title }}",
"author": {
"@type": "Person",
"name": "{{ metadata.author.name }}"
},
"datePublished": "{{ date }}",
"description": "{% if synopsis %}{{ synopsis}}{% endif %}",
"image": "{% if image-url %}{{ image-url | htmlBaseUrl(metadata.url) }}{% else %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endif %}",
"url": "{{ page.url | htmlBaseUrl(metadata.url) }}",
"articleBody": "{{ content | striptags }}"
}
</script>
{% endif %}
</head>
<body>
<a href="#skip" class="visually-hidden">Skip to main content</a>
<header>
<a href="/" class="home-link">{{ metadata.title }}</a>
<a href="/" class="home-link"><img src="{{ metadata.logo }}" alt="{{ metadata.title }}"></a>
{#- Read more about `eleventy-navigation` at https://www.11ty.dev/docs/plugins/navigation/ #}
<nav>
<h2 class="visually-hidden">Top level navigation menu</h2>
<ul class="nav">
{%- for entry in collections.all | eleventyNavigation %}
<li class="nav-item"><a href="{{ entry.url }}"{% if entry.url == page.url %} aria-current="page"{% endif %}>{{ entry.title }}</a></li>
<li class="nav-item" {% if entry.url == page.url %} data-currentpage="true"{% endif %}><a href="{{ entry.url }}"{% if entry.url == page.url %} aria-current="page"{% endif %}>{{ entry.title }}</a></li>
{%- endfor %}
<li class="nav-item subscribe"><a href="/feed/feed.xml"><img class="nav-icon" src="/img/RSS.svg">Subscribe</a></li>
</ul>
</nav>
</header>

View File

@ -1,16 +1,8 @@
---
layout: layouts/base.njk
showPostListHeader: yep
---
<!-- Delete this message, it will also remove the component CSS from the bundle -->
{%- css %}{% include "public/css/message-box.css" %}{% endcss %}
<div class="message-box">
<ol>
<li>Edit the <code>_data/metadata.js</code> with your blogs information.</li>
<li>(Optional) Edit <code>eleventy.config.js</code> with your <a href="https://www.11ty.dev/docs/config/">configuration preferences</a>.</li>
<li>Delete this message from <code>_includes/layouts/home.njk</code>.</li>
</ol>
<p><em>This is an <a href="https://www.11ty.dev/">Eleventy project</a> created from the <a href="https://github.com/11ty/eleventy-base-blog"><code>eleventy-base-blog</code> repo</a>.</em></p>
</div>
<!-- Stop deleting -->
<h1>The personal website and blog of Nathan Upchurch.</h1>
<p class="nodropcap page-block">Welcome to my personal website and blog, where I write about tech, free and open source software, design, vegan cooking, incense, music, and all sorts of <a href="/tags">other topics</a> that I find interesting.</p>
{{ content | safe }}

View File

@ -10,7 +10,7 @@ layout: layouts/base.njk
<li><time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time></li>
{%- for tag in tags | filterTagList %}
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a>{%- if not loop.last %}, {% endif %}</li>
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a></li>
{%- endfor %}
</ul>
@ -24,5 +24,6 @@ layout: layouts/base.njk
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a></li>{% endif %}
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a></li>{% endif %}
</ul>
<p>Did this post make you feel happy, sad, or angry? Come praise, cry, or yell at me on <a href="https://mastodon.social/@nathanu">Mastodon</a>.</p>
{%- endif %}
{%- endif %}

View File

@ -1,9 +1,23 @@
{%- css %}.postlist { counter-reset: start-from {{ (postslistCounter or postslist.length) + 1 }} }{% endcss %}
<ol reversed class="postlist">
{% for post in postslist | reverse %}
<li class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
<a href="{{ post.url }}" class="postlist-link">{% if post.data.title %}{{ post.data.title }}{% else %}<code>{{ post.url }}</code>{% endif %}</a>
<time class="postlist-date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate("LLLL yyyy") }}</time>
</li>
{% endfor %}
</ol>
<section class="postlist">
{% if showPostListHeader %}<h2>Latest Posts</h2>{% endif %}
<div class="postlist-item-container">
{% for post in postslist | reverse %}
<article class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
<a href="{{ post.url }}" class="postlist-link">
<div class="post-image-container">
<img class="post-image" {% if post.data.imageURL %} src="{{ post.data.imageURL }}" alt="{{ post.data.imageAlt }}" {% else %} src="{{ metadata.defaultPostImageURL }}" alt="{{ metadata.defaultPostImageAlt }}"{% endif %}>
</div>
</a>
<div class="post-copy">
<a href="{{ post.url }}" class="postlist-link">
<h3>
{% if post.data.title %}{{ post.data.title }}{% else %}<code>{{ post.url }}</code>{% endif %}
</h3>
</a>
<time class="postlist-date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate("LLLL yyyy") }}</time>
{% if post.data.synopsis %}<p>{{ post.data.synopsis | truncate(110) | safe }}</p>{% else %}{{ post.content | truncate(140) | safe }}{% endif %}
</div>
</article>
{% endfor %}
</div>
</section>

View File

@ -1,19 +1,5 @@
---
layout: layouts/home.njk
layout: layouts/404.njk
permalink: 404.html
eleventyExcludeFromCollections: true
---
# Content not found.
Go <a href="/">home</a>.
<!--
Read more: https://www.11ty.dev/docs/quicktips/not-found/
This will work for both GitHub pages and Netlify:
* https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
* https://www.netlify.com/docs/redirects/#custom-404
-->

19
content/404.md.backup Normal file
View File

@ -0,0 +1,19 @@
---
layout: layouts/home.njk
permalink: 404.html
eleventyExcludeFromCollections: true
---
# Content not found.
Go <a href="/">home</a>.
<!--
Read more: https://www.11ty.dev/docs/quicktips/not-found/
This will work for both GitHub pages and Netlify:
* https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
* https://www.netlify.com/docs/redirects/#custom-404
-->

View File

@ -1,9 +1,8 @@
---
layout: layouts/base.njk
eleventyNavigation:
key: About Me
key: About
order: 3
---
# About Me
I am a person that writes stuff.
# About the author, Nathan Upchurch.
<p class="page-block nodropcap">I'm a prolific vegan home cook, classical trombonist, a <abbr title="Free/Libre Open Source Software">FLOSS</abbr> enthusiast, daily GNU/Linux user and unabashed <a href="https://kde.org/">KDE</a> stan, speaker of subpar elementary Spanish, incense enthusiast, writer, electronics hobbyist, designer, programmer, music producer, print lover, and human with too many interests and too little time. This is my personal website and blog. <br><br>Because this is my <em>personal</em> website, I'm not here to talk about work, but I will link my professional bio here when I've gotten around to making it. If you would like to say something nice, you can find me on <a href="https://mastodon.social/@nathanu">Mastodon</a>.<br><br>This website is made with <a href="https://www.11ty.dev/">11ty</a>, <a href="https://www.gent.media/manrope">Manrope</a>, <a href="https://github.com/clauseggers/Playfair">Playfair Display</a>, and plain-old HTML & CSS. I don't collect any of your data, full-stop. No analytics, no "anonymized data," nothing. All webfonts, icons, and images are hosted locally (Instead of by Google, for instance, or any other company which might<a href="https://www.firstpost.com/world/how-google-uses-fonts-to-track-what-users-do-online-and-sell-data-to-advertisers-12496552.html"> use them to track you</a>).<br><hr>Blogs are the soul of the web! To find more, visit <a href="https://blogroll.org">blogroll.org</a>, or <a href="https://ooh.directory/">ooh.directory</a>.</p>

View File

@ -1,10 +1,14 @@
---
layout: layouts/home.njk
layout: layouts/base.njk
eleventyNavigation:
key: Archive
key: Blog
order: 2
---
<h1>Archive</h1>
<h1>Nathan Upchurchs Personal Blog: Latest Posts.</h1>
<p class="page-block nodropcap">
Looking for something in particular? Have a look at <a href="/tags/">this convenient list of post categories</a> to filter results by topic.
</p>
{% set postslist = collections.posts %}
{% include "postslist.njk" %}

View File

@ -0,0 +1,451 @@
---
title: Build an SVG Circle Grid with p5.js
description: Make a configurable SVG graphic of a grid of circles in random colors and sizes with p5.js.
date: 2023-06-18
tags:
- Processing
- p5.js
- Code Tutorial
- SVG
synopsis: In this tutorial, we'll learn how to make a configurable SVG graphic of a grid of circles in random colors and sizes with p5.js.
imageURL: /img/terminal.svg
imageAlt: A stylized illustration of a terminal prompt.
---
Processing is a fantastic language for creative programming and learning how to code, allowing programmers of all skill levels to quickly and simply create complex graphics, data visualizations, and generative art. Its Javascript implementation, p5.js, is perfect for those already familiar with Javascript, or who want to use processing to make graphics for the web without the complexity of SVG, or the insanity of using CSS for complex graphics. Today we're going to build [a simple but pretty graphic using P5](#et-voila).
## Our goal
First, let's define exactly what we're going to be making. We want to make a grid of circles that:
1. Fills the viewport
2. Randomly assigns a size to each circle
3. Randomly assigns a color to each circle
4. Is flexible and avoids hardcoded values, allowing us to get a variety of different looks depending on our parameters
5. Allows us to download our generated image as an SVG
With that nailed down, let's go ahead and get set up.
## Setup
Processing has a handy <abbr title="Integrated Development Environment">IDE</abbr>, much like Arduino, that we can use to get started quickly. Processing IDE is available as a Flatpak, so it should be simple to install no matter what distro you're running. If, sadly, you're on Windows or MacOS, I'm sure there is also a simple way to install it on your machine that can be worked out with a quick search on the internet.
Once you've installed and launched the IDE, at the top right you'll notice a dropdown that says "Java." This is the Processing IDE's mode selector. Click the dropdown, and choose "Manage Modes." In the new window, install "p5.js Mode," and switch to p5.js using the mode selector.
## Getting started
If you've done everything correctly up to now, you should see two tabs in your IDE: a .js file, and index.html. Our .js file should look like this:
``` javascript
function setup() {
}
function draw() {
}
```
and our index.html should look like this:
``` html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- PLEASE NO CHANGES BELOW THIS LINE (UNTIL I SAY SO) -->
<script language="javascript" type="text/javascript" src="libraries/p5.min.js"></script>
<script language="javascript" type="text/javascript" src="sketch_230615e.js"></script>
<!-- OK, YOU CAN MAKE CHANGES BELOW THIS LINE AGAIN -->
<style>
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
</body>
</html>
```
In any processing sketch, there are two main functions, as you can see by looking at our .js file. `setup()` will run once when the sketch is loaded, and `draw()` will loop repeatedly. For our purposes, we don't need `draw()`, so we'll just leave it empty.
Now that we have our boilerplate, the next thing we need to do is set up the canvas for our sketch. As you may recall, we want our sketch to fill the viewport, so let's set up a canvas inside of our `setup()` function like so:
``` javascript
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
background(0);
noStroke();
}
```
Now if we click the play icon at the top left of the Processing IDE, a browser window should open and load a page showing our sketch so far. We ought to see a viewport entirely covered with one large black canvas.
[![The LibreWolf web browser opened to localhost. The viewport is entirely black.](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-canvas.webp)](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-canvas.webp)
## Random results
To meet conditions two and three of our goal, we'll need a way to get random numbers. Let's write a quick function inside `setup()` to provide us with random integers:
``` javascript
const getRandomInt = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
```
## Let's make some circles
To make a circle in p5, we need to define three parameters: x position, y position, and its diameter. With that in mind, we can define a circle like so:
``` javascript
circle(10,10,10);
```
Add one to the bottom of `setup()` and refresh your browser tab if you'd like to test it out. Be sure and delete it afterwards.
We're going to be making a grid of circles, however, so we are going to need a little more information. First, let's start by generating a line of circles. Although we only need to generate a line of circles on one axis for the purpose of making a grid, a row or a column, for the sake of future flexibility, we'll assume that our line might be horizontal, vertical, or diagonal. To do this, we'll need the following information:
* Line start position x
* Line start position y
* Distance between circles on x
* Distance between circles on y
* Minimum circle diameter
* Maximum circle diameter
* Quantity of circles
* The axis upon which our line extends
* An array of colors for our circles
You'll notice that we have min-max values for circle diameter, and an array for our circle colors. This is so that a size and color can be chosen randomly by our program.
With that out of the way, let's start writing a function to generate our line of circles inside `setup()`:
``` javascript
const generateCircleLine = (startX, startY, distX, distY, minD, maxD, qty, axis, fillArr) => {
}
```
We are going to need to increment our starting coordinates, so let's assign two variables to those parameters:
``` javascript
let x = startX;
let y = startY;
```
And we can write our loop inside of `generateCircleLine()`:
``` javascript
for (let i = 0; i < qty; i++) {
const diameter = getRandomInt(minD-1, maxD);
if (!i) {
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
continue;
}
switch (axis) {
case 'x':
x = !distX ? x + startX : x + distX;
break;
case 'y':
y = !distY ? y + startY : y + distY;
break;
case 'xy':
x = !distX ? x + startX : x + distX;
y = !distY ? y + startY : y + distY;
break;
}
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
}
```
In the loop above:
* We start by assigning `diameter` to a random integer between `minD` and `maxD`
* Then, if `i` is false / 0, indicating that we're on our first iteration, we randomly choose a fill color value from `fillArr`, draw our first circle, and continue on to our next iteration.
* Now that we're on iteration 1 / circle 2, we need to figure out what axis or axes our line is on, and calculate the new starting coordinates for the circle about to be drawn. For this, we use a switch statement, so if we are operating on the x asis, only the x starting coordinate will be incremented, et cetera. We have used the ternary operator here to indicate that if a distance is not specified along any given axis, the program should increment by a distance equal to its starting coordinate.
* As we now have our coordinates, we choose a random fill color from fillArr, draw our next circle, and repeat until `i < qty`.
That was a lot, so let's look at our code and make sure everything is alright. This is what our code should look like at the moment:
``` javascript
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
background(0);
noStroke();
const getRandomInt = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const generateCircleLine = (startX, startY, distX, distY, minD, maxD, qty, axis, fillArr) => {
let x = startX;
let y = startY;
for (let i = 0; i < qty; i++) {
const diameter = getRandomInt(minD-1, maxD);
if (!i) {
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
continue;
}
switch (axis) {
case 'x':
x = !distX ? x + startX : x + distX;
break;
case 'y':
y = !distY ? y + startY : y + distY;
break;
case 'xy':
x = !distX ? x + startX : x + distX;
y = !distY ? y + startY : y + distY;
break;
}
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
}
}
}
function draw() {
}
```
We can check that everything is working by calling our new function at the bottom of `setup()`:
``` javascript
generateCircleLine(10, 10, 20, 0, 5, 10, 10, 'x', [[255,255,255]]);
```
[![The LibreWolf web browser opened to localhost. The viewport is black, with a single row of ten white circles in varying sizes in the top-left corner.](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-line.webp)](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-line.webp)
And look at that! We have a line!
## Repeating rows
As we now have a function built to generate lines of circles, all we need to do is repeat the process and we'll have a grid. In `setup()`, let's go ahead and create `generateCircleGrid()` to do just that. Because we'll be passing our parameter values from `generateCircleGrid()` to `generateCircleLine()`, we'll need all of the same information, plus a few new parameters:
``` javascript
const generateCircleGrid = (rowStartX, rowStartY, rowDistX, minD, maxD, rowCircleQty, numRows, rowSpacing, fillArr) => {
}
```
Inside our new function, all we need to do is call `generateCircleLine()` in a loop and we'll have ourselves a grid:
``` javascript
for (let i = 0; i < numRows; i++) {
rowYPosition = !i ? rowStartY : rowStartY + rowSpacing*i;
generateCircleLine(rowStartX, rowYPosition, rowDistX, 0, minD, maxD, rowCircleQty, 'x', fillArr);
}
```
In the first line of our for loop, we're checking whether we're in the first iteration (`i == 0`). If so, `rowYPosition` is assigned to rowStartY; otherwise, `rowYPosition` is assigned to `rowStartY + rowSpacing*i`. That means that on our second+ iterations, our row starting coordinate is incremented by our row spacing value multiplied by the current iteration number, ensuring that each iteration draws a row at the correct y coordinate.
We've hardcoded 'x' in the `generateCircleLine()` parameter, so that we only have to worry about incrementing one axis as we generate our grid: `generateCircleLine()` will create columns, and `generateCircleGrid()` loops them to generate rows. To this end, we're also passing 0 to the `distY` in generateCircleLine()`.
## The perfect fit
So, we can create lines of circles, and use those to make a grid, but we have a problem. Right now, our sketch needs to be explicitly told how many circles to draw in each row. We need a way to determine how many circles ought to be drawn in order to fill the canvas on a given axis.
Fortunately, this is an easy fix. If write a function inside `setup()` to subtract our starting coordinate from the window dimension for a given axis, divide the result by the distance between circles, and return the result, we should wind up with just the right nimber of circles to fit within that axis. We'll also give our function a parameter called `axis`. Using this parameter, we'll specify whether the operation should use `window.innerWidth` to return a result for the x axis, or `window.innerHeight` to return a result for the y axis.
``` javascript
const getMaxCircles = (distance, axis, startDistance)=> {
return Math.floor(axis == 'x' ? (window.innerWidth - startDistance) / distance :
(window.innerHeight - startDistance) / distance);
}
```
## Specifying the grid
We're now all set up to begin actually generating grids, so let's make one! To keep things neat, let's specify our grid in an object called `myGrid` that contains all the parameters we'll need. Here's what mine looks like:
``` javascript
const myGrid = {
rowStartX: 20,
rowStartY: 20,
rowDistX: 20,
minDiameter: 5,
maxDiameter: 15,
rowSpacing: 20,
rowCircleQty: function() {return getMaxCircles(this.rowDistX, 'x', this.rowStartX)},
numRows: function() {return getMaxCircles(this.rowSpacing, 'y', this.rowStartY)},
fillArray: [
[100, 50, 255],
[100, 100, 255],
[100, 150, 255],
[100, 200, 255],
[100, 250, 255]
]
}
```
You may notice a couple of things:
* I've taken advantage of the `getMaxCircles()` function we wrote earlier to calculate ideal values for `myGrid.rowCircleQty`, and `myGrid.numRows`.
* `myGrid.fillArray` is using RGB values. Check the [p5.js documentation](https://p5js.org/reference/#/p5/fill) for other color values you can use.
Feel free to customize `myGrid` to make your ideal circle grid, and call `generateCircleGrid()` to see the result:
``` javascript
generateCircleGrid(
myGrid.rowStartX,
myGrid.rowStartY,
myGrid.rowDistX,
myGrid.minDiameter,
myGrid.maxDiameter,
myGrid.rowCircleQty(),
myGrid.numRows(),
myGrid.rowSpacing,
myGrid.fillArray
);
```
[![The LibreWolf web browser opened to localhost. The viewport is filled with circles of varying sizes in various shades of blue.](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-grid.webp)](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-grid.webp)
## SVG export
Looking back on our goals, we've accomplished all but one: SVG export. Happily, there's [a library](https://github.com/zenozeng/p5.js-svg) that makes this trivial. All we need to do is link the script in the `<head>` of index.html,
``` html
<script language="javascript" type="text/javascript" src="https://unpkg.com/p5.js-svg@1.5.1"></script>
```
add a button in `<body>`,
``` html
<button onclick="save()">Save SVG</button>
```
style it in `<style>`,
``` css
button {
position: fixed;
bottom: 3rem;
right: 3rem;
width: 7rem;
height: 3rem;
}
```
and make one small tweak in our Javascript file. We simply need to modify our `createCanvas()` function like so:
``` javascript
createCanvas(window.innerWidth, window.innerHeight, SVG);
```
## Our finished code
Here's what everything should look like at this point:
### *.js
``` javascript
function setup() {
createCanvas(window.innerWidth, window.innerHeight, SVG);
background(0);
noStroke();
const getRandomInt = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
const getMaxCircles = (distance, axis, startDistance)=> {
return Math.floor(axis == 'x' ? (window.innerWidth - startDistance) / distance :
(window.innerHeight - startDistance) / distance);
}
const generateCircleLine = (startX, startY, distX, distY, minD, maxD, qty, axis, fillArr) => {
let x = startX;
let y = startY;
for (let i = 0; i < qty; i++) {
const diameter = getRandomInt(minD-1, maxD);
if (!i) {
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
continue;
}
switch (axis) {
case 'x':
x = !distX ? x + startX : x + distX;
break;
case 'y':
y = !distY ? y + startY : y + distY;
break;
case 'xy':
x = !distX ? x + startX : x + distX;
y = !distY ? y + startY : y + distY;
break;
}
fill(...fillArr[getRandomInt(0, fillArr.length)]);
circle(x,y,diameter);
}
}
const generateCircleGrid = (rowStartX, rowStartY, rowDistX, minD, maxD, rowCircleQty, numRows, rowSpacing, fillArr) => {
for (let i = 0; i < numRows; i++) {
rowYPosition = !i ? rowStartY : rowStartY + rowSpacing*i;
generateCircleLine(rowStartX, rowYPosition, rowDistX, 0, minD, maxD, rowCircleQty, 'x', fillArr);
}
}
const myGrid = {
rowStartX: 20,
rowStartY: 20,
rowDistX: 20,
minDiameter: 5,
maxDiameter: 15,
rowSpacing: 20,
rowCircleQty: function() {return getMaxCircles(this.rowDistX, 'x', this.rowStartX)},
numRows: function() {return getMaxCircles(this.rowSpacing, 'y', this.rowStartY)},
fillArray: [
[100, 50, 255],
[100, 100, 255],
[100, 150, 255],
[100, 200, 255],
[100, 250, 255]
]
}
generateCircleGrid(
myGrid.rowStartX,
myGrid.rowStartY,
myGrid.rowDistX,
myGrid.minDiameter,
myGrid.maxDiameter,
myGrid.rowCircleQty(),
myGrid.numRows(),
myGrid.rowSpacing,
myGrid.fillArray
);
}
function draw() {
}
```
### index.html
``` html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- PLEASE NO CHANGES BELOW THIS LINE (UNTIL I SAY SO) -->
<script language="javascript" type="text/javascript" src="libraries/p5.min.js"></script>
<script language="javascript" type="text/javascript" src="sketch_230615e.js"></script>
<script language="javascript" type="text/javascript" src="https://unpkg.com/p5.js-svg@1.5.1"></script>
<!-- OK, YOU CAN MAKE CHANGES BELOW THIS LINE AGAIN -->
<style>
body {
padding: 0;
margin: 0;
}
button {
position: fixed;
bottom: 3rem;
right: 3rem;
width: 7rem;
height: 3rem;
}
</style>
</head>
<body>
<button onclick="save()">Save SVG</button>
</body>
</html>
```
## Et voilà
And we're done! Now you can tweak the parameters and make grids with all sorts of colors and size variations, and even export and edit them in Inkscape!
[![The LibreWolf web browser opened to localhost. The viewport is filled with circles of varying sizes in various shades of blue. There is a button reading "Save SVG" in the bottom right corner.](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-complete.webp)](../../img/posts/build-an-svg-circle-grid-with-p5js/circle-grid-complete.webp)
If you'd like to make this project even better, maybe consider implementing a GUI to adjust your grid paramaters, or adding some interactivity.

View File

@ -1,6 +0,0 @@
---
title: This is a fifth post (draft)
date: 2023-01-23
draft: true
---
This is a draft post

View File

@ -1,26 +0,0 @@
---
title: This is my first post.
description: This is a post on My Blog about agile frameworks.
date: 2018-05-01
tags:
- another tag
---
Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.
Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.
## Section Header
Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.
```diff-js
// this is a command
function myCommand() {
+ let counter = 0;
- let counter = 1;
counter++;
}
// Test with a line break above this line.
console.log('Test');
```

View File

@ -0,0 +1,28 @@
---
title: Free Software is the Future for SMEs and Small Nonprofits
description: Tech giants aren't meeting the needs of SMEs and nonprofits. Combined with digital privacy concerns, a perfect storm is brewing for increased adoption of free and open source software for these organizations.
date: 2022-10-28
tags:
- FOSS/FLOSS
- Digital Privacy
synopsis: Tech giants aren't meeting the needs of SMEs and nonprofits. Combined with digital privacy concerns, a perfect storm is brewing for increased adoption of free and open source software for these organizations.
imageURL: /img/posts/floss-and-smes/pedro-lastra-Nyvq2juw4_o-unsplash.webp
imageAlt: A stylized illustration of a terminal prompt.
---
If you're a cottage industry solopreneur, a cricut hobbyist, or a makerspace regular, you've probably heard of Inkscape. Often dismissed by design professionals as a poor substitute for fully fleshed out design tools or simply "not industry standard," this 19 year old vector graphics powerhouse allows artists, designers, scrapbookers and makers alike to create high quality graphics and illustrations that can be used anywhere, from the browser to printed collateral and laser-etched goods. Inkscape, like many other free and open source software (FOSS) projects, has been taking great strides in recent years to match, if not surpass, commercial alternatives in terms of features, output, and usability. FOSS users have long been privy to a a world of software all but unknown to those who have never ventured from the comfort of Microsoft, Adobe, Alphabet, and Oracle, or the shimmering walled-garden of the Apple ecosystem. It hasn't always been easy, pretty, or even stable, but these days free and open source software projects are more organized, better funded, and more accountable than ever before, and people are starting to take notice.
## FOSS offers a level of flexibility and transparency that proprietary mainstays can't or won't provide.
As free and open source software becomes increasingly attractive to small businesses and nonprofits due to expanding feature-sets and improved user-interfaces, the principles at the core of its creation, perhaps counterintuitively, render projects such as Inkscape ideal for a business environment. While those unfamiliar with the concept of FOSS projects may interpret "free" to reflect its pricing structure, developers can and do charge for access to their products. Instead, the "F" in FOSS represents the freedom to run the program however you want, to study how it works, to change it, and to redistribute copies. FOSS software, in addition, must also publish its source code publicly. This radically transparent approach, beyond the obvious implication that FOSS software is vastly more flexible than proprietary mainstays, can have a tremendous effect on security and privacy, both key with software present on devices containing sensitive employee information, intellectual property, and credentials. While the dialogue surrounding digital privacy has almost exclusively focused on individual users, as we're now aware of the ability of marketers, campaigns, ([or even John Oliver](https://www.youtube.com/watch?v=wqn3gR1WTcA)) to purchase highly targeted, fine-grained data for any purpose, it's only a matter of time before more organizations begin to consider the implications of the potential for proprietary software to indiscriminately collect employee web-searches, map routes, downloads, and discussions to turn into data-points available for purchase by anyone with the budget, such as competitors, marketers, journalists, or government and regulatory bodies, domestic and abroad.
This is where FOSS starts to make a lot more sense from a privacy perspective; because human-readable source code is generally transformed, or compiled, into a more efficient language that can only be read by computers before software is distributed to users, FOSS software authors publish their human-readable source code publicly, enabling anyone to review it. In contrast, companies behind proprietary software do not publicly publish their source code, making it almost impossible to understand precisely how their software works, or what it's doing at any given time. We already know that just about all big names in tech collect private data; we either shrug our shoulders and make peace with it, or we suffer through innumerable privacy policies, settings, sliders, and toggles, and still itch a little afterwards. But what about the tools we use every day that don't have a privacy policy? What about your picture viewer, your file browser, or even your calculator app? The stringent efforts of even the most militant IT departments to block users from installing unapproved software are moot in the face of the fact that we have no way of truly knowing what even the most fundamental software tools from trusted brands are actually doing on the devices that we use to do our work each day. By enabling public access to its source code, free and *open source* software can see have dozens, hundreds or even thousands of tech-savvy users pointing out security flaws to be fixed, alerting developers to bugs, and raising alarm bells about potential privacy issues.
## Individual users and small organizations can directly influence development of the FOSS tools they use every day.
The focus on transparency in FOSS software engenders a culture of collaboration all but alien to organizations accustomed to proprietary solutions. Successful projects build communities of developers and other contributors, who are each free to work in their own way. Some contributors are individuals who stop by to implement a feature they wish a project had, while others are teams paid by large enterprises to improve open source projects that form a part of their digital infrastructure. Some individuals simply work on FOSS projects as a hobby, and others, like [Inkscape's Martin Owens](https://www.youtube.com/channel/UCPxxdsRV92DZGE-RcRsw_gw), are freelance developers who collect donations to work on bug fixes and features using donor feedback to prioritize their tasks and steer their work. Martin regularly uploads videos on his [YouTube channel](https://www.youtube.com/channel/UCPxxdsRV92DZGE-RcRsw_gw) and [Patreon](https://www.patreon.com/doctormo) to fill in donors on the latest Inkscape updates and development work, and uses polls to allow them to vote on which features should be prioritized for the next release. Donors can message him directly, for technical support, questions, or suggestions. Developers on Inkscape and other FOSS projects can be reached via public mailing lists and chat channels, and software users often interact directly with them via bug reports and feature requests. This makes each user a potential collaborator in the development of the tools they use.
Only a few weeks ago, I filed [a feature request](https://bugs.kde.org/show_bug.cgi?id=454674) for [Kasts](https://apps.kde.org/kasts/), a desktop app that syncs and plays podcasts. After a discussion with one of the developers, they eventually sent me a link to test the feature, which is now in the latest release. This culture of collaboration represents a unique opportunity for SMEs and nonprofits that may not have the budget for custom enterprise solutions. So long as organizations understand and respect the culture, a reasonable donation budget and a willingness to collaborate can open avenues for smaller organizations to work with developers, freelancers, designers, and others within FOSS projects to work towards better experiences and expanded feature sets. Whether this takes the shape of donating a sum in support of the project, and participating by filing competent bug reports and feature requests, or directly commissioning a developer on the project to design and implement a needed feature, needed funds are usually a far cry away from big-budget proprietary-software customizations, and having the option to even speak to a developer at all is a breath of fresh air when compared to the usual experience dealing with sales representatives and account managers. Of course, commercial users of free and open source software must temper their expectations; as with proprietary software, there are no guarantees that the project will go in the direction you'd like, and FOSS projects have no obligation to provide support or develop suggested features no matter how much you donate.
## Many FOSS projects have long been ready for professional and enterprise use.
Beyond privacy, security, and collaboration potential, FOSS alternatives are getting better by the day. Many open source projects are being developed at a breakneck pace while receiving major corporate support, and projects strictly manned by volunteers are more organized than ever before. An uptick in donations sees some projects considering diverting a portion of donated funds to outsourcing some of the more arduous development tasks less likely to be quickly tackled by volunteers. While FOSS projects such as GNU/Linux based operating systems and the software written for them have long played a critical role in the world's digital infrastructure, often even serving as the base upon which commercial products are built, free software is no longer resigned to the server room, with consumer products such as Valve's Steam Deck being based on projects such as the Arch Linux desktop operating system and KDE's Plasma desktop environment, a computer graphical user interface that looks good enough to compete with large commercial players like Microsoft's Windows Shell and Apple's Aqua, and flogs both of them when it comes to flexibility and customization. It is not uncommon for free-of-charge FOSS projects such as Inkscape to now boast a feature-set comparable, or as in the case of Inkscape, even exceeding that of their paid competitors, and large communities such as those of GNOME and KDE have developed extensive collections of software that can take the place of hundreds of proprietary software tools at zero cost.
## As organizations begin to see FOSS benefits, big tech is likely to begin losing market share.
While big names in tech continue to erode user privacy and ownership of their software via expanding data collection and subscription models that see users unable to use a PDF reader without connecting to the internet, knowledge of FOSS projects is becoming less and less of the domain of the hyper tech-literate. Our world has already become one in which feature-rich, fast, and well designed free and open source software can replace most black-box proprietary solutions, and for SMEs and nonprofits in the know, the flexibility, privacy expectations, and price-point makes FOSS an extremely attractive proposition. Tech giants won't lose sluggish enterprise clients any time soon, but the writing is on the wall for the many smaller organizations without the budgets for multi-million dollar IT contracts, unless they do something fast.

View File

@ -1,16 +0,0 @@
---
title: This is my fourth post.
description: This is a post on My Blog about touchpoints and circling wagons.
date: 2018-09-30
tags: second tag
---
Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.
Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.
{% image "./possum.png", "A possum parent and two possum kids hanging from the iconic red balloon" %}
## Section Header
Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

View File

@ -0,0 +1,50 @@
---
title: How to Transfer Files Securely with the “scp” Command
description: How to transfer files to and from a remote Server with the scp / secure copy command
synopsis: How to transfer files to and from a remote Server with the scp / secure copy command
date: 2023-06-12
tags:
- GNU/Linux
- Tutorial
- CLI Tools
imageURL: /img/terminal.svg
imageAlt: A stylized illustration of a terminal prompt.
---
Copying files to and from remote servers is a common chore that can initially seem uncommonly tricky. Thankfully, we have the `scp` command, also known as the secure copy command, as a simple way to initiate secure transfers to and from your remote boxes.
## First, make sure your permissions are in order
Just like when you `ssh` into your server, when using `scp`, you need to ensure that the user you intend to use to log in to the remote server has access to the files and directories you'll need to access on your remote machine. For instance, if you want to copy a file from a remote server that only the root user has access to, you will need to either:
1. Specify the root user in your `scp` command;
2. `ssh` into the remote server as root and edit the file's permissions and / or move it somewhere accessible to the user you will specify in your `scp` command;
3. `ssh` into the remote server as a user with sudo permissions and edit the file's permissions and / or move it somewhere accessible to the user you will specify in your scp command;
Hopefully, you have disabled remote root login to your server via `ssh` for security reasons, so you will use option three, if necessary, prior to executing your `scp` command.
Using `scp` is very similar to using the `ssh` command. Both tools share some flags, and their commands look similar, the key difference being that `scp` is not interactive; every action you wish to take on your remote machine must be encapsulated in your `scp` command, which is why you must first ensure that the user specified in your `scp` command has access to any files or directories you want to transfer to or from.
## Examples
### Copying from a remote server
Just like you might when logging in via the `ssh` command, if you've changed your default `ssh` port to something other than 22, you'll need to specify it with the `-P` flag; if you're using key-based authentication, you'll also need to specify your key with the `-i` flag:
``` bash
scp -P [port] -i [keyPath] [remoteUser]@[remoteServerNameOrIP]:[filePath] [downloadPath]
```
### Copying to a remote server
When uploading, your command should look like this, with the local file path before your username and server address:
``` bash
scp -P [port] -i [keyPath] [filePath] [remoteUser]@[remoteServerNameOrIP]:[uploadPath]
```
### Other options
Copy the contents of a directory recursively using the `-r` flag:
``` bash
scp -r [remoteUser]@[remoteServerNameOrIP]:[filePath] [downloadPath]
```
Copy a file between two remote hosts directly:
``` bash
scp [remoteUser1]@[remoteServerNameOrIP1]:[filePath] [remoteUser2]@[remoteServerNameOrIP2]:[filePath]
Copy a file between two remote hosts, routing through your local machine:
``` bash
scp -3 [remoteUser1]@[remoteServerNameOrIP1]:[filePath] [remoteUser2]@[remoteServerNameOrIP2]:[filePath]
```
### That's all there is to it.
And there you have it: one simple command to transfer files to and from your remote servers. So long as your remote users have the prerequisite permissions to access the correct remote files and directories, and there's nothing wrong with your `ssh` setup, transferring files with `scp` is a breeze.

View File

@ -1,17 +0,0 @@
---
title: This is my second post with a much longer title.
description: This is a post on My Blog about leveraging agile frameworks.
date: 2018-07-04
tags:
- number 2
---
Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.
## Section Header
<a href="/blog/firstpost/">First post</a>
<a href="/blog/thirdpost/">Third post</a>
Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.
Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.

View File

@ -1,45 +0,0 @@
---
title: This is my third post.
description: This is a post on My Blog about win-win survival strategies.
date: 2018-08-24
tags:
- second tag
- posts with two tags
---
Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.
## Code
### Styled (with Syntax)
Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.
```js
// this is a command
function myCommand() {
let counter = 0;
counter++;
}
// Test with a line break above this line.
console.log('Test');
```
### Unstyled
Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring.
```
// this is a command
function myCommand() {
let counter = 0;
counter++;
}
// Test with a line break above this line.
console.log('Test');
```
## Section Header
Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.

View File

@ -16,11 +16,18 @@ permalink: /feed/feed.xml
</author>
{%- for post in collections.posts | reverse %}
{% set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %}
{% if post.image-url %}{% set imageURL %}{{ post.image-url | htmlBaseUrl(metadata.url) }}{% endset %}{% endif %}
{% set defaultImageURL %}{{ metadata.defaultPostImageURL | htmlBaseUrl(metadata.url) }}{% endset %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}"/>
<updated>{{ post.date | dateToRfc3339 }}</updated>
<id>{{ absolutePostUrl }}</id>
<image>
<url>{% if post.image-url %}{{ imageURL }}{% else %}{{ defaultImageURL }}{% endif %}</url>
<title>{% if post.image-alt %}{{ post.image-alt }}{% else %}{{ metadata.defaultPostImageAlt }}{% endif %}</title>
<link href="{{ absolutePostUrl }}"/>
</image>
<content type="html">{{ post.templateContent | transformWithHtmlBase(absolutePostUrl, post.url) }}</content>
</entry>
{%- endfor %}

View File

@ -7,15 +7,13 @@ numberOfLatestPostsToShow: 3
---
{% set postsCount = collections.posts | length %}
{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %}
<h1>Latest {{ latestPostsCount }} Post{% if latestPostsCount != 1 %}s{% endif %}</h1>
{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %}
{% set postslistCounter = postsCount %}
{% include "postslist.njk" %}
{% set morePosts = postsCount - numberOfLatestPostsToShow %}
{% if morePosts > 0 %}
<p>{{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} can be found in <a href="/blog/">the archive</a>.</p>
<p>See {{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} in <a href="/blog/">the blog</a>.</p>
{% endif %}
{# List every content page in the project #}

View File

@ -1,10 +1,14 @@
---
permalink: /tags/
layout: layouts/home.njk
layout: layouts/base.njk
---
<h1>Tags</h1>
<h1>Here are some things I like to talk about.</h1>
<ul>
<p class="page-block nodropcap">
Click on a tag to see all posts on the topic.
</p>
<ul class="taglist">
{% for tag in collections.all | getAllTags | filterTagList %}
{% set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a></li>

View File

@ -9,14 +9,16 @@ pagination:
- posts
- tagList
addAllPagesToCollections: true
layout: layouts/home.njk
layout: layouts/base.njk
eleventyComputed:
title: Tagged “{{ tag }}”
permalink: /tags/{{ tag | slugify }}/
---
<h1>Tagged “{{ tag }}”</h1>
<h1>More posts about “{{ tag }}.”</h1>
<p class="page-block nodropcap">Here's everything I've posted about {{ tag }}:</p>
{% set postslist = collections[ tag ] %}
{% include "postslist.njk" %}
<p>See <a href="/tags/">all tags</a>.</p>
<p class="nodropcap">See <a href="/tags/">all tags</a>.</p>

View File

@ -14,7 +14,7 @@ module.exports = eleventyConfig => {
eleventyConfig.addAsyncShortcode("image", async function imageShortcode(src, alt, widths, sizes) {
// Full list of formats here: https://www.11ty.dev/docs/plugins/image/#output-formats
// Warning: Avif can be resource-intensive so take care!
let formats = ["avif", "webp", "auto"];
let formats = ["avif", "webp", "png", "auto"];
let file = relativeToInputPath(this.page.inputPath, src);
let metadata = await eleventyImage(file, {
widths: widths || ["auto"],

View File

@ -1,4 +1,6 @@
const { DateTime } = require("luxon");
const markdownIt = require("markdown-it");
const markdownItFootnote = require("markdown-it-footnote");
const markdownItAnchor = require("markdown-it-anchor");
const pluginRss = require("@11ty/eleventy-plugin-rss");
@ -89,7 +91,7 @@ module.exports = function(eleventyConfig) {
}),
level: [1,2,3,4],
slugify: eleventyConfig.getFilter("slugify")
});
}).use(markdownItFootnote);
});
// Features to make your build faster (when you need them)

View File

@ -40,5 +40,8 @@
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"luxon": "^3.3.0",
"markdown-it-anchor": "^8.6.7"
},
"dependencies": {
"markdown-it-footnote": "^3.0.3"
}
}

15
public/css/dropcap.css Normal file
View File

@ -0,0 +1,15 @@
main > p:not(.nodropcap):first-of-type:first-letter {
float: left;
font-size: 4rem;
font-weight: 300;
line-height: 60px;
padding: .5rem .5rem .5rem .5rem;
color: var(--contrast-color);
border: solid 2px var(--contrast-color);
font-family: 'Playfair Display';
font-weight: 700;
margin-right: .25em;
border-radius: .2em;
border-top-left-radius: 0;
margin-bottom: -.2em;
}

View File

@ -1,23 +1,36 @@
/* Defaults */
:root {
--font-family: -apple-system, system-ui, sans-serif;
--font-family: Manrope;
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
}
/* Theme colors */
/* Tokens */
:root {
/* Colors */
--color-gray-20: #e0e0e0;
--color-gray-50: #C0C0C0;
--color-gray-90: #333;
--color-gray-90: #333333;
--background-color: #fff;
--background-color: #FAF5F5;
--text-color: var(--color-gray-90);
--text-color-link: #082840;
--text-color-link-active: #5f2b48;
--text-color-link-visited: #17050F;
--text-color-link: var(--color-gray-90);
--text-color-link-active: rgba(3,144,116,1);
--text-color-link-visited: var(--color-gray-90);
--contrast-color: rgba(3,144,116,1);
--nav-pill-background-color-active: rgba(3,144,116,1);
--nav-pill-background-color-inactive: rgba(71,71,71,1);
--nav-pill-text-color: rgba(255,255,255,1);
--nav-pill-text-color-link-visited: rgba(255,255,255,1);
--nav-pill-text-color-link-active: rgba(255,255,255,1);
/* Space & Size */
--syntax-tab-size: 2;
--pill-radius: 5rem;
--card-radius: .3rem;
}
@media (prefers-color-scheme: dark) {
@ -25,17 +38,17 @@
--color-gray-20: #e0e0e0;
--color-gray-50: #C0C0C0;
--color-gray-90: #dad8d8;
--card-color: #333333;
/* --text-color is assigned to --color-gray-_ above */
--text-color-link: #1493fb;
--text-color-link-active: #6969f7;
--text-color-link-visited: #a6a6f8;
--text-color-link: rgba(3,144,116,1);
--text-color-link-active: rgba(3,144,116,1);
--text-color-link-visited: rgba(3,144,116,1);
--background-color: #15202b;
}
}
/* Global stylesheet */
* {
box-sizing: border-box;
@ -46,6 +59,8 @@ body {
padding: 0;
margin: 0 auto;
font-family: var(--font-family);
font-weight: 300;
font-variant-Ligatures: normal;
color: var(--text-color);
background-color: var(--background-color);
}
@ -56,6 +71,15 @@ body {
max-width: 40em;
}
a {
text-decoration: wavy underline;
text-decoration-color: var(--contrast-color);
text-decoration-thickness: .1rem;
text-decoration-skip: none;
transition: all .5s;
font-weight: 700;
}
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
.visually-hidden {
clip: rect(0 0 0 0);
@ -67,15 +91,39 @@ body {
width: 1px;
}
h1 {
font-family: 'Playfair Display';
font-weight: 700;
font-size: 3.75rem;
font-style: normal;
line-height: 4rem;
padding-top: 3rem;
margin-bottom: 0;
}
@media(max-width: 556px) {
h1 {
font-size: 3rem;
padding-top: 0;
}
}
p:last-child {
margin-bottom: 0;
}
p {
line-height: 1.5;
p, li {
line-height: 1.75;
font-size: 14pt;
}
li {
line-height: 1.5;
main > p > img, main > p > a > img {
width: 100%;
padding-top: 2rem;
}
.page-block {
margin-top: 3rem;
margin-bottom: 3rem;
}
a[href] {
@ -92,23 +140,26 @@ a[href]:active {
main {
padding: 1rem;
}
main h2 {
font-weight: 700;
font-size: 2em;
margin-top: 2em;
}
main :first-child {
margin-top: 0;
}
header {
border-bottom: 1px dashed var(--color-gray-20);
}
header:after {
content: "";
display: table;
clear: both;
border-bottom: 1px solid var(--color-gray-20);
}
.links-nextprev {
list-style: none;
border-top: 1px dashed var(--color-gray-20);
border-top: 1px solid var(--color-gray-20);
padding: 1em 0;
margin-top: 2rem;
}
table {
@ -147,56 +198,113 @@ header {
display: flex;
gap: 1em .5em;
flex-wrap: wrap;
align-items: center;
align-items: end;
padding: 1em;
}
@media(max-width: 556px) {
header {
padding: 1rem .5rem 1rem .5rem;
}
}
.home-link {
font-size: 1em; /* 16px /16 */
font-weight: 700;
margin-right: 2em;
display: flex;
align-items: end;
}
.home-link:link:not(:hover) {
text-decoration: none;
}
.home-link img {
width: 3rem;
}
/* Nav */
.nav {
display: flex;
padding: 0;
margin: 0;
list-style: none;
}
.nav-icon {
max-width: .5rem;
margin-right: .5rem;
}
.nav-item {
display: inline-block;
margin-right: 1em;
margin-right: 2em;
margin-bottom: 0;
text-transform: uppercase;
letter-spacing: .2rem;
font-size: .6rem;
font-weight: 500;
background-color: var(--nav-pill-background-color-inactive);
padding: .55em .85em .55em 1.2em;
border-radius: .75em;
color: var(--nav-pill-text-color);
transition: all .5s;
}
.subscribe:hover {
background-color: rgba(225,90,0,1);
color: var(--nav-pill-text-color);
}
.nav-item a[href]:visited {
color: var(--nav-pill-text-color-link-visited);
}
.nav-item a[href]:hover {
text-decoration: none;
color: var(--nav-pill-text-color);
}
.nav-item a[href]:not(:hover) {
text-decoration: none;
color: var(--nav-pill-text-color);
}
.nav-item[data-currentpage="true"] {
background-color: var(--nav-pill-background-color-active);
}
.nav a[href][aria-current="page"] {
text-decoration: underline;
background-color: var(--nav-pill-background-color-active);
color: var(--nav-pill-text-color);
}
@media(max-width: 556px) {
.nav {
flex-flow: row wrap;
}
.nav-item {
margin-right: 1rem;
margin-bottom: .5rem;
}
}
/* Posts list */
.postlist {
list-style: none;
padding: 0;
padding-left: 1.5rem;
.postlist h2 {
margin-bottom: 2rem;
}
.postlist-item-container {
display: flex;
flex-flow: column nowrap;
}
.postlist-item {
display: flex;
flex-wrap: wrap;
align-items: baseline;
counter-increment: start-from -1;
flex-flow: row nowrap;
align-items: flex-start;
justify-content: flex-start;
margin-bottom: 1em;
width: 100%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
padding: 1rem 1.1rem 1rem 1.1rem;
border-radius: var(--card-radius);
background-color: white;
}
.postlist-item:before {
display: inline-block;
pointer-events: none;
content: "" counter(start-from, decimal-leading-zero) ". ";
line-height: 100%;
text-align: right;
margin-left: -1.5rem;
.post-image-container {
border-radius: var(--card-radius);
margin-right: 1rem;
overflow: hidden;
height: 10em;
}
.postlist-date,
.postlist-item:before {
@ -209,40 +317,115 @@ header {
.postlist-link {
font-size: 1.1875em; /* 19px /16 */
font-weight: 700;
flex-basis: calc(100% - 1.5rem);
padding-left: .25em;
padding-right: .5em;
text-underline-position: from-font;
text-underline-offset: 0;
text-decoration-thickness: 1px;
text-decoration: none;
}
.postlist-link h3 {
margin-bottom: 0;
}
.postlist-item-active .postlist-link {
font-weight: bold;
}
.post-copy {
display: flex;
flex-flow: column wrap;
}
.post-image {
width: 10em;
height: 10em;
object-fit: cover;
object-position: 50% 50%;
}
@media(max-width: 556px) {
.postlist {
padding-top: 0;
}
.postlist-item {
flex-flow: column wrap;
}
.postlist-link {
padding: 0;
}
.post-copy a h3 {
margin-top: .5em;
}
.post-image {
width: 100%;
height: auto;
}
.post-image-container {
margin-right: 0;
height: 10em;
min-width: 100%;
}
}
@media (prefers-color-scheme: dark) {
.postlist-item {
background-color: var(--card-color);
}
}
/* Tags */
.post-tag {
.post-tag, .taglist li a {
display: inline-flex;
align-items: center;
justify-content: center;
text-transform: capitalize;
font-style: italic;
background-color: var(--color-gray-20);
text-decoration: none;
padding: .2rem .4rem .2rem .4rem;
border-radius: var(--pill-radius);
font-size: .75rem;
}
.taglist {
list-style-type: none;
padding-left: 0;
display: flex;
flex-flow: row wrap;
}
.taglist li {
margin: 0em .5em 1.25em 0em;
}
.taglist li a {
font-size: 1.25rem;
text-decoration-color: var(--contrast-color);
text-decoration-thickness: .1rem;
text-decoration-skip: none;
transition: all .5s;
font-weight: 700;
padding: .4rem .8rem .4rem .8rem;
}
.postlist-item > .post-tag {
align-self: center;
}
@media(max-width: 556px) {
.taglist {
flex-flow: column nowrap;
}
}
/* Tags list */
.post-metadata {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: .5em;
list-style: none;
padding: 0;
margin: 0;
padding: 0 0 0 .4em;
font-size: .8em;
margin-bottom: 5em;
}
.post-metadata time {
margin-right: 1em;
margin-right: .5em;
font-size: 1em;
}
@media(max-width: 556px) {
.post-metadata {
margin-bottom: 1em;
}
}
/* Direct Links / Markdown Headers */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More