diff --git a/.editorconfig b/.editorconfig index 1807aa45..6b6553be 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,7 @@ indent_size = 4 [*.{ym,yml,yaml,js,json,scss}] indent_style = space indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95b3f912..55b6cd2f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ yarn-error.log .phpunit.result.cache .DS_Store .nova + +site-static-html/ diff --git a/composer.json b/composer.json index f52ccaab..7e1569c9 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "nesbot/carbon": "^2.53", "symfony/console": "^5.3", "symfony/finder": "^5.3", - "league/flysystem": "^2.3" + "league/flysystem": "^2.3", + "nyholm/psr7-server": "^1.0" }, "scripts": { "prod": "@production", diff --git a/composer.lock b/composer.lock index d9dfde28..c94705a3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4763364ea23c35f2aa09a8ce3f274597", + "content-hash": "08b16e0749832418f94aabef446ddc96", "packages": [ { "name": "8fold/commonmark-abbreviations", @@ -1159,6 +1159,72 @@ ], "time": "2021-07-02T08:32:20+00:00" }, + { + "name": "nyholm/psr7-server", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/b846a689844cef114e8079d8c80f0afd96745ae3", + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2021-05-12T11:11:27+00:00" + }, { "name": "php-http/message-factory", "version": "v1.0.2", diff --git a/content/README.md b/content/README.md new file mode 100644 index 00000000..7637cdda --- /dev/null +++ b/content/README.md @@ -0,0 +1,13 @@ +# Content for JoshBruce.com + +This repository holds some of the writings of Joshua C. Bruce; specifically, those used for [joshbruce.com](https://joshbruce.com). + +This project is one aspect of a broader conversation and possible community. + +Anyone may [contribute to](https://github.com/joshbruce/content-joshbruce.com/blob/main/.github/CONTRIBUTING.md) improving the content per the [license agreement](https://github.com/joshbruce/content-joshbruce.com/blob/main/.github/LICENSE) and in keeping with the [code of conduct](https://github.com/joshbruce/content-joshbruce.com/blob/main/.github/CODE_OF_CONDUCT.md). + +You may interact with and [sponsor](https://github.com/sponsors/joshbruce) this project through GitHub using the sponsor link, the discussion and issues tabs, and contributing directly. + +You can also find me [@itsjoshbruce](https://twitter.com/ItsJoshBruce) on Twitter. + +The code used to run the site is [over here](https://github.com/8fold/site-joshbruce.com). diff --git a/content/assets/js/.gitkeep b/content/assets/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/content/assets/sass/main.scss b/content/assets/sass/main.scss new file mode 100644 index 00000000..e81094ef --- /dev/null +++ b/content/assets/sass/main.scss @@ -0,0 +1,407 @@ +:root { + --header-font: "Gill Sans", "Gill Sans MT", Calibri, sans-serif; + --body-font: "Open Sans", "Helvetica Neue", Verdana, sans-serif; + --code-font: Consolas, monaco, monospace; + + --x-bold-font: 600; + --bold-font: 500; + --medium-font: 300; + --light-font: 250; + + --2xl-font: clamp(30pt, 9.375vw, 35pt); + --xl-font: clamp(25pt, 7.8125vw, 30pt); + --l-font: clamp(20pt, 6.25vw, 25pt); + --m-font: clamp(16pt, 5vw, 18pt); + --s-font: clamp(13pt, 4.0625vw, 15pt); + --xs-font: clamp(10pt, 3.125vw, 14pt); + + --l-line-height: calc(var(--2xl-font) * 1); + --m-line-height: calc(var(--s-font) * 1.62); + --s-line-height: calc(var(--s-font) * 1.25); + + --line-length: 70ch; + + --key: #0A6276; + --key-light: #5FCAF2; + --gray-darkest: #030303; + --gray-darker: #0F2124; + --gray-lightest: #FCFDFD; + + --vw-0: 0vw; + + --0-spacer: 0; + --xl-spacer: 4rem; + --l-spacer: 2rem; + --m-spacer: 1rem; + --s-spacer: 0.75rem; + --xs-spacer: 0.5rem; + --2xs-spacer: 0.25rem; + --3xs-spacer: 0.1rem; + --1-px: 1px; + --2-px: 2px; + --3-px: 3px; + --400-px: 400px; + + --icon-new-window: "⧉"; + --icon-increase: "◭"; + --icon-decrease: "⧩"; + --icon-hold: "≅"; + --icon-lift-spacer: -0.5rem; + + --margin-centered: var(--0-spacer) var(--auto); + + --smooth: smooth; + --auto: auto; + --pointer: pointer; + --none: none; + --transition-color: color 0.25s; + --center: center; + --left: left; + --inline-block: inline-block; + --solid: solid; + --dashed: dashed; + --transparent: transparent; + --relative: relative; + --absolute: absolute; + --italic: italic; + --block: block; + --both: both; + --hidden: hidden; +} + +body { + min-height: var(--vw-0); + max-width: var(--line-length); + margin: var(--margin-centered); + background: var(--gray-lightest); + color: var(--gray-darkest); + font-family: var(--body-font); + font-size: var(--m-font); + line-height: var(--m-line-height); + font-weight: var(--light-font); + + & > a { + position: var(--relative); + text-align: var(--center); + font-size: var(--s-font); + display: var(--block); + margin: var(--l-spacer); + font-weight: var(--light-font); + padding: var(--m-spacer); + + &[id="content-top"] { + &:before { + display: var(--inline-block); + content: var(--icon-decrease); + position: var(--absolute); + left: var(--0-spacer); + } + + &:after { + display: var(--inline-block); + content: var(--icon-decrease); + position: var(--absolute); + right: var(--0-spacer); + } + } + + &[id="go-to-top"] { + &:before { + display: var(--inline-block); + content: var(--icon-increase); + position: var(--absolute); + left: var(--0-spacer); + } + + &:after { + display: var(--inline-block); + content: var(--icon-increase); + position: var(--absolute); + right: var(--0-spacer); + } + } + } +} + +a:hover { + cursor: var(--pointer); +} + +a { + font-weight: var(--bold-font); + text-decoration: var(--none); + color: var(--key); + transition: var(--transition-color); + + &:hover { + color: var(--gray-darker); + } +} + +[is="dateblock"] { + margin-bottom: var(--l-spacer); + + & p { + margin: var(--0-spacer); + font-size: var(--xs-font); + + & > time { + font-weight: var(--bold-font); + } + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: var(--l-spacer); + margin-bottom: var(--xs-spacer); + font-family: var(--head-font); + font-size: clamp(30pt, 9.375vw, 35pt); + // font-size: var(--2xl-font); + line-height: var(--l-line-height); + + + ol, + ul, + p { + margin-top: var(--0-spacer); + padding-top: var(--0-spacer); + } +} + +h1 { + margin-top: var(--m-spacer); +} + +h2 { + font-size: var(--xl-font); +} + +h3, +h4, +h5, +h6 { + font-size: var(--l-font); + text-align: var(--center); +} + +hr { + width: 33%; + border: var(--2-px) var(--solid) var(--gray-darkest); + border-radius: var(--1-px); +} + +code { + font-family: var(--code-font); + font-size: var(--s-font); +} + +.toc { + padding: var(--none); + list-style: var(--none); + + li { + border-bottom: var(--1-px) var(--solid) var(--gray-darker); + + &:last-child { + border-bottom: var(--none); + } + } + + & h3 { + margin: var(--xs-spacer); + text-align: var(--left); + font-size: var(--l-font); + + & a { + display: var(--block); + padding: var(--m-spacer); + } + + & small { + font-size: var(--s-font); + display: var(--block); + color: var(--gray-darker); + font-weight: var(--light-font); + } + } +} + +main, article { + padding: var(--0-spacer) var(--m-spacer); + padding-top: var(--m-spacer); + + img, + iframe[src*="https://jsfiddle.net"] { + display: var(--block); + margin: var(--margin-centered); + width: 80%; + border: var(--3-px) var(--solid) var(--gray-darker); + } + + iframe[src*="https://jsfiddle.net"] { + min-height: 300px; + } + + abbr { + text-decoration: var(--none); + border-bottom: var(--1-px) var(--dashed) var(--gray-darker); + } + + dl { + & > dt { + margin-top: var(--m-spacer); + } + + & > dd { + margin-bottom: var(--s-spacer); + } + } + + details { + border: var(--1-px) var(--solid) var(--key); + border-radius: var(--3-px); + margin-top: var(--m-spacer); + padding: .5em .5em 0; + font-weight: var(--bold-font); + + & summary { + color: var(--key); + cursor: var(--pointer); + margin: -.5em -.5em 0; + padding: .5em; + } + } +} + +nav > ul { + margin: var(--0-spacer); + padding: var(--0-spacer); + list-style: var(--none); + + > li:first-of-type { + margin-bottom: var(--m-spacer); + } + + & ul { + list-style: var(--none); + margin: var(--0-spacer) var(--0-spacer); + padding: var(--0-spacer) var(--0-spacer); + & a { + font-size: var(--s-font); + } + + li:last-of-type a { + padding-bottom: var(--s-spacer); + } + } + + & a { + display: var(--block); + overflow: var(--hidden); + text-align: var(--center); + position: var(--relative); + } + + abbr { + border-color: var(--gray-lightest); + } +} + + +@media (prefers-reduced-motion: no-preference) { + html { + scroll-behavior: var(--smooth); // not available in Safari - graceful degradation + } + + nav > ul a { + &:after { + content: ''; + position: var(--absolute); + width:25%; + border-top: var(--1-px) var(--solid) var(--gray-darker); + opacity: 0.25; + top:60%; + left:-100%; + transition-delay: all 0.75s; + transition: all 0.75s ease-out; + } + + &:hover:after { + left:100%; + } + } +} + +@media (prefers-color-scheme: dark) { + body { + background: var(--gray-darkest); + color: var(--gray-lightest); + font-weight: var(--medium-font); + } + + hr { + border: 1px solid var(--gray-lightest); + } + + a { + color: var(--key-light); + font-weight: var(--x-bold-font); + + &:hover { + color: var(--gray-lightest); + } + } + + [is="dateblock"] p > time, b, strong { + font-weight: var(--x-bold-font); + } + + nav > ul a:after { + border-top: var(--1-px) var(--solid) var(--gray-lightest); + } + + main, article { + img, + iframe[src*="https://jsfiddle.net"] { + border: 3px solid var(--gray-lightest); + } + + details { + font-weight: var(--bold-font); + + & summary { + color: var(--key-light); + } + } + } +} + +blockquote { + margin: var(--m-spacer); + font-style: var(--italic); +} + +.heading-permalink { + display: var(--inline-block); + margin-right: var(--2xs-spacer); +} + +a[rel~=noreferrer]:after { + content: var(--icon-new-window); + display: var(--inline-block); + margin-left: var(--2xs-spacer); + position: var(--relative); + top: var(--icon-lift-spacer); + font-size: var(--xs-font); +} + +footer { + margin-top: var(--l-spacer); + & > p { + text-align: var(--center); + font-size: var(--xs-font); + } +} + diff --git a/content/navigation/main.md b/content/navigation/main.md new file mode 100644 index 00000000..c7bc557b --- /dev/null +++ b/content/navigation/main.md @@ -0,0 +1,13 @@ +- [Josh bruce](/) +- [Finances](/finances) + - [Investment policy](/finances/investment-policy) + - [Paycheck to paycheck](/finances/building-wealth-paycheck-to-paycheck) +- [Design your life](/design-your-life) + - [Motivators](/design-your-life/motivators) +- [Software development](/software-development) + - [Why don't you use](/software-development/why-dont-you-use) +- [Web development](/web-development) + - [On constraints](/web-development/on-constraints) + - [Modern web development](/web-development/modern-web-development) + - [Static, dynamic, and interactive](/web-development/static-dynamic-and-interactive) + - [Refactoring, re-engineering, and rebuilding](/web-development/refactoring-re-engineering-and-rebuilding) diff --git a/content/notices/original.md b/content/notices/original.md new file mode 100644 index 00000000..8b1225cf --- /dev/null +++ b/content/notices/original.md @@ -0,0 +1,2 @@ +This content was originally posted on {!!platformlink!!}. There may be modifications and updates in comparison. +{class="notice"} diff --git a/content/public/.htaccess b/content/public/.htaccess new file mode 100644 index 00000000..92c843c0 --- /dev/null +++ b/content/public/.htaccess @@ -0,0 +1,5 @@ +Options -Indexes + +ErrorDocument 404 /error-404.html +ErrorDocument 405 /error-405.html +ErrorDocument 500 /error-500.html diff --git a/content/public/assets/css/main.css b/content/public/assets/css/main.css new file mode 100644 index 00000000..cc3bd7f9 --- /dev/null +++ b/content/public/assets/css/main.css @@ -0,0 +1,372 @@ +@charset "UTF-8"; +:root{ + --header-font:"Gill Sans", "Gill Sans MT", Calibri, sans-serif; + --body-font:"Open Sans", "Helvetica Neue", Verdana, sans-serif; + --code-font:Consolas, monaco, monospace; + --x-bold-font:600; + --bold-font:500; + --medium-font:300; + --light-font:250; + --2xl-font:clamp(30pt, 9.375vw, 35pt); + --xl-font:clamp(25pt, 7.8125vw, 30pt); + --l-font:clamp(20pt, 6.25vw, 25pt); + --m-font:clamp(16pt, 5vw, 18pt); + --s-font:clamp(13pt, 4.0625vw, 15pt); + --xs-font:clamp(10pt, 3.125vw, 14pt); + --l-line-height:calc(var(--2xl-font) * 1); + --m-line-height:calc(var(--s-font) * 1.62); + --s-line-height:calc(var(--s-font) * 1.25); + --line-length:70ch; + --key:#0A6276; + --key-light:#5FCAF2; + --gray-darkest:#030303; + --gray-darker:#0F2124; + --gray-lightest:#FCFDFD; + --vw-0:0vw; + --0-spacer:0; + --xl-spacer:4rem; + --l-spacer:2rem; + --m-spacer:1rem; + --s-spacer:0.75rem; + --xs-spacer:0.5rem; + --2xs-spacer:0.25rem; + --3xs-spacer:0.1rem; + --1-px:1px; + --2-px:2px; + --3-px:3px; + --400-px:400px; + --icon-new-window:"⧉"; + --icon-increase:"◭"; + --icon-decrease:"⧩"; + --icon-hold:"≅"; + --icon-lift-spacer:-0.5rem; + --margin-centered:var(--0-spacer) var(--auto); + --smooth:smooth; + --auto:auto; + --pointer:pointer; + --none:none; + --transition-color:color 0.25s; + --center:center; + --left:left; + --inline-block:inline-block; + --solid:solid; + --dashed:dashed; + --transparent:transparent; + --relative:relative; + --absolute:absolute; + --italic:italic; + --block:block; + --both:both; + --hidden:hidden; +} + +body{ + min-height:var(--vw-0); + max-width:var(--line-length); + margin:var(--margin-centered); + background:var(--gray-lightest); + color:var(--gray-darkest); + font-family:var(--body-font); + font-size:var(--m-font); + line-height:var(--m-line-height); + font-weight:var(--light-font); +} +body > a{ + position:var(--relative); + text-align:var(--center); + font-size:var(--s-font); + display:var(--block); + margin:var(--l-spacer); + font-weight:var(--light-font); + padding:var(--m-spacer); +} +body > a[id=content-top]:before{ + display:var(--inline-block); + content:var(--icon-decrease); + position:var(--absolute); + left:var(--0-spacer); +} +body > a[id=content-top]:after{ + display:var(--inline-block); + content:var(--icon-decrease); + position:var(--absolute); + right:var(--0-spacer); +} +body > a[id=go-to-top]:before{ + display:var(--inline-block); + content:var(--icon-increase); + position:var(--absolute); + left:var(--0-spacer); +} +body > a[id=go-to-top]:after{ + display:var(--inline-block); + content:var(--icon-increase); + position:var(--absolute); + right:var(--0-spacer); +} + +a:hover{ + cursor:var(--pointer); +} + +a{ + font-weight:var(--bold-font); + -webkit-text-decoration:var(--none); + text-decoration:var(--none); + color:var(--key); + transition:var(--transition-color); +} +a:hover{ + color:var(--gray-darker); +} + +[is=dateblock]{ + margin-bottom:var(--l-spacer); +} +[is=dateblock] p{ + margin:var(--0-spacer); + font-size:var(--xs-font); +} +[is=dateblock] p > time{ + font-weight:var(--bold-font); +} + +h1, +h2, +h3, +h4, +h5, +h6{ + margin-top:var(--l-spacer); + margin-bottom:var(--xs-spacer); + font-family:var(--head-font); + font-size:clamp(30pt, 9.375vw, 35pt); + line-height:var(--l-line-height); +} +h1 + ol, h1 + ul, h1 + p, +h2 + ol, +h2 + ul, +h2 + p, +h3 + ol, +h3 + ul, +h3 + p, +h4 + ol, +h4 + ul, +h4 + p, +h5 + ol, +h5 + ul, +h5 + p, +h6 + ol, +h6 + ul, +h6 + p{ + margin-top:var(--0-spacer); + padding-top:var(--0-spacer); +} + +h1{ + margin-top:var(--m-spacer); +} + +h2{ + font-size:var(--xl-font); +} + +h3, +h4, +h5, +h6{ + font-size:var(--l-font); + text-align:var(--center); +} + +hr{ + width:33%; + border:var(--2-px) var(--solid) var(--gray-darkest); + border-radius:var(--1-px); +} + +code{ + font-family:var(--code-font); + font-size:var(--s-font); +} + +.toc{ + padding:var(--none); + list-style:var(--none); +} +.toc li{ + border-bottom:var(--1-px) var(--solid) var(--gray-darker); +} +.toc li:last-child{ + border-bottom:var(--none); +} +.toc h3{ + margin:var(--xs-spacer); + text-align:var(--left); + font-size:var(--l-font); +} +.toc h3 a{ + display:var(--block); + padding:var(--m-spacer); +} +.toc h3 small{ + font-size:var(--s-font); + display:var(--block); + color:var(--gray-darker); + font-weight:var(--light-font); +} + +main, article{ + padding:var(--0-spacer) var(--m-spacer); + padding-top:var(--m-spacer); +} +main img, +main iframe[src*="https://jsfiddle.net"], article img, +article iframe[src*="https://jsfiddle.net"]{ + display:var(--block); + margin:var(--margin-centered); + width:80%; + border:var(--3-px) var(--solid) var(--gray-darker); +} +main iframe[src*="https://jsfiddle.net"], article iframe[src*="https://jsfiddle.net"]{ + min-height:300px; +} +main abbr, article abbr{ + -webkit-text-decoration:var(--none); + text-decoration:var(--none); + border-bottom:var(--1-px) var(--dashed) var(--gray-darker); +} +main dl > dt, article dl > dt{ + margin-top:var(--m-spacer); +} +main dl > dd, article dl > dd{ + margin-bottom:var(--s-spacer); +} +main details, article details{ + border:var(--1-px) var(--solid) var(--key); + border-radius:var(--3-px); + margin-top:var(--m-spacer); + padding:0.5em 0.5em 0; + font-weight:var(--bold-font); +} +main details summary, article details summary{ + color:var(--key); + cursor:var(--pointer); + margin:-0.5em -0.5em 0; + padding:0.5em; +} + +nav > ul{ + margin:var(--0-spacer); + padding:var(--0-spacer); + list-style:var(--none); +} +nav > ul > li:first-of-type{ + margin-bottom:var(--m-spacer); +} +nav > ul ul{ + list-style:var(--none); + margin:var(--0-spacer) var(--0-spacer); + padding:var(--0-spacer) var(--0-spacer); +} +nav > ul ul a{ + font-size:var(--s-font); +} +nav > ul ul li:last-of-type a{ + padding-bottom:var(--s-spacer); +} +nav > ul a{ + display:var(--block); + overflow:var(--hidden); + text-align:var(--center); + position:var(--relative); +} +nav > ul abbr{ + border-color:var(--gray-lightest); +} + +@media (prefers-reduced-motion: no-preference){ + html{ + scroll-behavior:var(--smooth); + } + + nav > ul a:after{ + content:""; + position:var(--absolute); + width:25%; + border-top:var(--1-px) var(--solid) var(--gray-darker); + opacity:0.25; + top:60%; + left:-100%; + transition-delay:all 0.75s; + transition:all 0.75s ease-out; + } + nav > ul a:hover:after{ + left:100%; + } +} +@media (prefers-color-scheme: dark){ + body{ + background:var(--gray-darkest); + color:var(--gray-lightest); + font-weight:var(--medium-font); + } + + hr{ + border:1px solid var(--gray-lightest); + } + + a{ + color:var(--key-light); + font-weight:var(--x-bold-font); + } + a:hover{ + color:var(--gray-lightest); + } + + [is=dateblock] p > time, b, strong{ + font-weight:var(--x-bold-font); + } + + nav > ul a:after{ + border-top:var(--1-px) var(--solid) var(--gray-lightest); + } + + main img, +main iframe[src*="https://jsfiddle.net"], article img, +article iframe[src*="https://jsfiddle.net"]{ + border:3px solid var(--gray-lightest); + } + main details, article details{ + font-weight:var(--bold-font); + } + main details summary, article details summary{ + color:var(--key-light); + } +} +blockquote{ + margin:var(--m-spacer); + font-style:var(--italic); +} + +.heading-permalink{ + display:var(--inline-block); + margin-right:var(--2xs-spacer); +} + +a[rel~=noreferrer]:after{ + content:var(--icon-new-window); + display:var(--inline-block); + margin-left:var(--2xs-spacer); + position:var(--relative); + top:var(--icon-lift-spacer); + font-size:var(--xs-font); +} + +footer{ + margin-top:var(--l-spacer); +} +footer > p{ + text-align:var(--center); + font-size:var(--xs-font); +} \ No newline at end of file diff --git a/content/public/assets/css/main.min.css b/content/public/assets/css/main.min.css new file mode 100644 index 00000000..be9e0d49 --- /dev/null +++ b/content/public/assets/css/main.min.css @@ -0,0 +1,2 @@ +@charset "UTF-8";:root{--header-font:"Gill Sans", "Gill Sans MT", Calibri, sans-serif;--body-font:"Open Sans", "Helvetica Neue", Verdana, sans-serif;--code-font:Consolas, monaco, monospace;--x-bold-font:600;--bold-font:500;--medium-font:300;--light-font:250;--2xl-font:clamp(30pt, 9.375vw, 35pt);--xl-font:clamp(25pt, 7.8125vw, 30pt);--l-font:clamp(20pt, 6.25vw, 25pt);--m-font:clamp(16pt, 5vw, 18pt);--s-font:clamp(13pt, 4.0625vw, 15pt);--xs-font:clamp(10pt, 3.125vw, 14pt);--l-line-height:calc(var(--2xl-font) * 1);--m-line-height:calc(var(--s-font) * 1.62);--s-line-height:calc(var(--s-font) * 1.25);--line-length:70ch;--key:#0A6276;--key-light:#5FCAF2;--gray-darkest:#030303;--gray-darker:#0F2124;--gray-lightest:#FCFDFD;--vw-0:0vw;--0-spacer:0;--xl-spacer:4rem;--l-spacer:2rem;--m-spacer:1rem;--s-spacer:0.75rem;--xs-spacer:0.5rem;--2xs-spacer:0.25rem;--3xs-spacer:0.1rem;--1-px:1px;--2-px:2px;--3-px:3px;--400-px:400px;--icon-new-window:"⧉";--icon-increase:"◭";--icon-decrease:"⧩";--icon-hold:"≅";--icon-lift-spacer:-0.5rem;--margin-centered:var(--0-spacer) var(--auto);--smooth:smooth;--auto:auto;--pointer:pointer;--none:none;--transition-color:color 0.25s;--center:center;--left:left;--inline-block:inline-block;--solid:solid;--dashed:dashed;--transparent:transparent;--relative:relative;--absolute:absolute;--italic:italic;--block:block;--both:both;--hidden:hidden}body{min-height:var(--vw-0);max-width:var(--line-length);margin:var(--margin-centered);background:var(--gray-lightest);color:var(--gray-darkest);font-family:var(--body-font);font-size:var(--m-font);line-height:var(--m-line-height)}body>a,code{font-size:var(--s-font)}body,body>a{font-weight:var(--light-font)}body>a{position:var(--relative);text-align:var(--center);display:var(--block);margin:var(--l-spacer);padding:var(--m-spacer)}body>a[id=content-top]:after,body>a[id=content-top]:before{display:var(--inline-block);content:var(--icon-decrease);position:var(--absolute)}body>a[id=content-top]:before{left:var(--0-spacer)}body>a[id=content-top]:after{right:var(--0-spacer)}body>a[id=go-to-top]:after,body>a[id=go-to-top]:before{display:var(--inline-block);content:var(--icon-increase);position:var(--absolute)}body>a[id=go-to-top]:before{left:var(--0-spacer)}body>a[id=go-to-top]:after{right:var(--0-spacer)}a:hover{cursor:var(--pointer);color:var(--gray-darker)}a{-webkit-text-decoration:var(--none);text-decoration:var(--none);color:var(--key);transition:var(--transition-color)}[is=dateblock]{margin-bottom:var(--l-spacer)}[is=dateblock] p{margin:var(--0-spacer);font-size:var(--xs-font)}[is=dateblock] p>time,a{font-weight:var(--bold-font)}h1,h2,h3,h4,h5,h6{margin-bottom:var(--xs-spacer);font-family:var(--head-font);font-size:clamp(30pt,9.375vw,35pt);line-height:var(--l-line-height)}h2,h3,h4,h5,h6{margin-top:var(--l-spacer)}h1+ol,h1+p,h1+ul,h2+ol,h2+p,h2+ul,h3+ol,h3+p,h3+ul,h4+ol,h4+p,h4+ul,h5+ol,h5+p,h5+ul,h6+ol,h6+p,h6+ul{margin-top:var(--0-spacer);padding-top:var(--0-spacer)}article dl>dt,h1,main dl>dt{margin-top:var(--m-spacer)}h2{font-size:var(--xl-font)}h3,h4,h5,h6{font-size:var(--l-font);text-align:var(--center)}hr{width:33%;border:var(--2-px) var(--solid) var(--gray-darkest);border-radius:var(--1-px)}code{font-family:var(--code-font)}.toc{padding:var(--none);list-style:var(--none)}.toc li{border-bottom:var(--1-px) var(--solid) var(--gray-darker)}.toc li:last-child{border-bottom:var(--none)}.toc h3{margin:var(--xs-spacer);text-align:var(--left);font-size:var(--l-font)}.toc h3 a{display:var(--block);padding:var(--m-spacer)}.toc h3 small{font-size:var(--s-font);display:var(--block);color:var(--gray-darker);font-weight:var(--light-font)}article,main{padding:var(--0-spacer) var(--m-spacer);padding-top:var(--m-spacer)}article iframe[src*="https://jsfiddle.net"],article img,main iframe[src*="https://jsfiddle.net"],main img{display:var(--block);margin:var(--margin-centered);width:80%;border:var(--3-px) var(--solid) var(--gray-darker)}article iframe[src*="https://jsfiddle.net"],main iframe[src*="https://jsfiddle.net"]{min-height:300px}article abbr,main abbr{-webkit-text-decoration:var(--none);text-decoration:var(--none);border-bottom:var(--1-px) var(--dashed) var(--gray-darker)}article dl>dd,main dl>dd{margin-bottom:var(--s-spacer)}article details,main details{border:var(--1-px) var(--solid) var(--key);border-radius:var(--3-px);margin-top:var(--m-spacer);padding:.5em .5em 0;font-weight:var(--bold-font)}article details summary,main details summary{color:var(--key);cursor:var(--pointer);margin:-.5em -.5em 0;padding:.5em}nav>ul{margin:var(--0-spacer);padding:var(--0-spacer);list-style:var(--none)}nav>ul>li:first-of-type{margin-bottom:var(--m-spacer)}nav>ul ul{list-style:var(--none);margin:var(--0-spacer) var(--0-spacer);padding:var(--0-spacer) var(--0-spacer)}nav>ul ul a{font-size:var(--s-font)}nav>ul ul li:last-of-type a{padding-bottom:var(--s-spacer)}footer>p,nav>ul a{text-align:var(--center)}nav>ul a{display:var(--block);overflow:var(--hidden);position:var(--relative)}nav>ul abbr{border-color:var(--gray-lightest)}@media (prefers-reduced-motion:no-preference){html{scroll-behavior:var(--smooth)}nav>ul a:after{content:"";position:var(--absolute);width:25%;border-top:var(--1-px) var(--solid) var(--gray-darker);opacity:.25;top:60%;left:-100%;transition-delay:all .75s;transition:all .75s ease-out}nav>ul a:hover:after{left:100%}}@media (prefers-color-scheme:dark){body{background:var(--gray-darkest);font-weight:var(--medium-font)}hr{border:1px solid var(--gray-lightest)}a:hover,body{color:var(--gray-lightest)}[is=dateblock] p>time,a,b,strong{font-weight:var(--x-bold-font)}nav>ul a:after{border-top:var(--1-px) var(--solid) var(--gray-lightest)}article iframe[src*="https://jsfiddle.net"],article img,main iframe[src*="https://jsfiddle.net"],main img{border:3px solid var(--gray-lightest)}article details,main details{font-weight:var(--bold-font)}a,article details summary,main details summary{color:var(--key-light)}}blockquote{margin:var(--m-spacer);font-style:var(--italic)}.heading-permalink{display:var(--inline-block);margin-right:var(--2xs-spacer)}a[rel~=noreferrer]:after{content:var(--icon-new-window);display:var(--inline-block);margin-left:var(--2xs-spacer);position:var(--relative);top:var(--icon-lift-spacer);font-size:var(--xs-font)}footer{margin-top:var(--l-spacer)}footer>p{font-size:var(--xs-font)} +/*# sourceMappingURL=main.min.css.map */ diff --git a/content/public/assets/css/main.min.css.map b/content/public/assets/css/main.min.css.map new file mode 100644 index 00000000..6dbb27c9 --- /dev/null +++ b/content/public/assets/css/main.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["main.css","../../../main.scss",""],"names":[],"mappings":"AAAA,gBAAgB,CCAhB,MACE,8DAAA,CACA,8DAAA,CACA,uCAAA,CAEA,iBAAA,CACA,eAAA,CACA,iBAAA,CACA,gBAAA,CAEA,qCAAA,CACA,qCAAA,CACA,kCAAA,CACA,+BAAA,CACA,oCAAA,CACA,oCAAA,CAEA,yCAAA,CACA,0CAAA,CACA,0CAAA,CAEA,kBAAA,CAEA,aAAA,CACA,mBAAA,CACA,sBAAA,CACA,qBAAA,CACA,uBAAA,CAEA,UAAA,CAEA,YAAA,CACA,gBAAA,CACA,eAAA,CACA,eAAA,CACA,kBAAA,CACA,kBAAA,CACA,oBAAA,CACA,mBAAA,CACA,UAAA,CACA,UAAA,CACA,UAAA,CACA,cAAA,CAEA,qBAAA,CACA,mBAAA,CACA,mBAAA,CACA,eAAA,CACA,0BAAA,CAEA,6CAAA,CAEA,eAAA,CACA,WAAA,CACA,iBAAA,CACA,WAAA,CACA,8BAAA,CACA,eAAA,CACA,WAAA,CACA,2BAAA,CACA,aAAA,CACA,eAAA,CACA,yBAAA,CACA,mBAAA,CACA,mBAAA,CACA,eAAA,CACA,aAAA,CACA,WAAA,CACA,eDRF,CCWA,KACE,sBAAA,CACA,4BAAA,CACA,6BAAA,CACA,+BAAA,CACA,yBAAA,CACA,4BAAA,CACA,uBAAA,CACA,gCDPF,CExEA,YDoME,sBAAA,ECpMF,YDwFI,4BAAA,EANF,AClFF,ODmFI,wBAAA,CACA,wBAAA,CAEA,oBAAA,CACA,sBAAA,CAEA,uBDRJ,CEjFA,2DDoGQ,2BAAA,CACA,4BAAA,CACA,uBAAA,EAVF,AC5FN,8BDgGQ,oBDTR,CCYM,6BAIE,qBDVR,CE7FA,uDDoHQ,2BAAA,CACA,4BAAA,CACA,uBAAA,EAVF,AC5GN,4BDgHQ,oBDbR,CCgBM,2BAIE,qBDdR,CCoBA,QACE,qBAAA,CAUE,uBAAA,CD3BJ,CCoBA,EAEE,mCAAA,CAAA,2BAAA,CACA,gBAAA,CACA,kCDjBF,CCwBA,eACE,6BDlBF,CCoBE,iBACE,sBAAA,CACA,wBDlBJ,CCoBI,wBACE,4BDlBN,CCuBA,kBAOE,8BAAA,CACA,4BAAA,CACA,kCAAA,CAEA,gCDrBF,CCUA,eAME,yBAAA,CDhBF,CCuBE,sGACE,0BAAA,CACA,2BDNJ,CCUA,4BACE,0BDPF,CCUA,GACE,wBDPF,CCUA,YAIE,uBAAA,CACA,wBDPF,CCUA,GACE,SAAA,CACA,mDAAA,CACA,yBDPF,CCUA,KACE,4BDNF,CCUA,KACE,mBAAA,CACA,sBDPF,CCSE,QACE,yDDPJ,CCSI,mBACE,yBDPN,CCWE,QACE,uBAAA,CACA,sBAAA,CACA,uBDTJ,CCWI,UACE,oBAAA,CACA,uBDTN,CCYI,cACE,uBAAA,CACA,oBAAA,CACA,wBAAA,CACA,6BDVN,CCeA,aACE,uCAAA,CACA,2BDZF,CCcE,0GAEE,oBAAA,CACA,6BAAA,CACA,SAAA,CACA,kDDXJ,CCcE,qFACE,gBDZJ,CCeE,uBACE,mCAAA,CAAA,2BAAA,CACA,0DDbJ,CCqBI,yBACE,6BDhBN,CCoBE,6BACE,0CAAA,CACA,yBAAA,CACA,0BAAA,CACA,mBAAA,CACA,4BDlBJ,CCoBI,6CACE,gBAAA,CACA,qBAAA,CACA,oBAAA,CACA,YDlBN,CCuBA,OACE,sBAAA,CACA,uBAAA,CACA,sBDpBF,CCsBE,wBACE,6BDpBJ,CCuBE,UACE,sBAAA,CACA,sCAAA,CACA,uCDrBJ,CCsBI,YACE,uBDpBN,CCuBI,4BACE,8BDrBN,CElRA,kBDkZI,uBAAA,EAvGF,AC3SF,SD4SI,oBAAA,CACA,sBAAA,CAEA,wBDvBJ,CC0BE,YACE,iCDxBJ,CC6BA,8CACE,KACE,6BD1BF,CC8BE,eACE,UAAA,CACA,wBAAA,CACA,SAAA,CACA,sDAAA,CACA,WAAA,CACA,OAAA,CACA,UAAA,CACA,yBAAA,CACA,4BD3BJ,CC8BE,qBACG,SD5BL,CACF,CCgCA,mCACE,KACE,8BAAA,CAEA,8BD9BF,CCiCA,GACE,qCD9BF,CCqCE,aACE,0BD9BJ,CCkCA,iCACM,8BD/BN,CCkCA,eACE,wDD/BF,CCmCE,0GAEE,qCD/BJ,CCkCE,6BACE,4BDhCJ,CCkCI,+CACE,sBDhCN,CACF,CCqCA,WACE,sBAAA,CACA,wBDnCF,CCsCA,mBACE,2BAAA,CACA,8BDnCF,CCsCA,yBACE,8BAAA,CACA,2BAAA,CACA,6BAAA,CACA,wBAAA,CACA,2BAAA,CACA,wBDnCF,CCsCA,OACE,0BDnCF,CCoCE,SAEE,wBDlCJ","file":"main.min.css","sourcesContent":["@charset \"UTF-8\";\n:root {\n --header-font: \"Gill Sans\", \"Gill Sans MT\", Calibri, sans-serif;\n --body-font: \"Open Sans\", \"Helvetica Neue\", Verdana, sans-serif;\n --code-font: Consolas, monaco, monospace;\n --x-bold-font: 600;\n --bold-font: 500;\n --medium-font: 300;\n --light-font: 250;\n --2xl-font: clamp(30pt, 9.375vw, 35pt);\n --xl-font: clamp(25pt, 7.8125vw, 30pt);\n --l-font: clamp(20pt, 6.25vw, 25pt);\n --m-font: clamp(16pt, 5vw, 18pt);\n --s-font: clamp(13pt, 4.0625vw, 15pt);\n --xs-font: clamp(10pt, 3.125vw, 14pt);\n --l-line-height: calc(var(--2xl-font) * 1);\n --m-line-height: calc(var(--s-font) * 1.62);\n --s-line-height: calc(var(--s-font) * 1.25);\n --line-length: 70ch;\n --key: #0A6276;\n --key-light: #5FCAF2;\n --gray-darkest: #030303;\n --gray-darker: #0F2124;\n --gray-lightest: #FCFDFD;\n --vw-0: 0vw;\n --0-spacer: 0;\n --xl-spacer: 4rem;\n --l-spacer: 2rem;\n --m-spacer: 1rem;\n --s-spacer: 0.75rem;\n --xs-spacer: 0.5rem;\n --2xs-spacer: 0.25rem;\n --3xs-spacer: 0.1rem;\n --1-px: 1px;\n --2-px: 2px;\n --3-px: 3px;\n --400-px: 400px;\n --icon-new-window: \"⧉\";\n --icon-increase: \"◭\";\n --icon-decrease: \"⧩\";\n --icon-hold: \"≅\";\n --icon-lift-spacer: -0.5rem;\n --margin-centered: var(--0-spacer) var(--auto);\n --smooth: smooth;\n --auto: auto;\n --pointer: pointer;\n --none: none;\n --transition-color: color 0.25s;\n --center: center;\n --left: left;\n --inline-block: inline-block;\n --solid: solid;\n --dashed: dashed;\n --transparent: transparent;\n --relative: relative;\n --absolute: absolute;\n --italic: italic;\n --block: block;\n --both: both;\n --hidden: hidden;\n}\n\nbody {\n min-height: var(--vw-0);\n max-width: var(--line-length);\n margin: var(--margin-centered);\n background: var(--gray-lightest);\n color: var(--gray-darkest);\n font-family: var(--body-font);\n font-size: var(--m-font);\n line-height: var(--m-line-height);\n font-weight: var(--light-font);\n}\nbody > a {\n position: var(--relative);\n text-align: var(--center);\n font-size: var(--s-font);\n display: var(--block);\n margin: var(--l-spacer);\n font-weight: var(--light-font);\n padding: var(--m-spacer);\n}\nbody > a[id=content-top]:before {\n display: var(--inline-block);\n content: var(--icon-decrease);\n position: var(--absolute);\n left: var(--0-spacer);\n}\nbody > a[id=content-top]:after {\n display: var(--inline-block);\n content: var(--icon-decrease);\n position: var(--absolute);\n right: var(--0-spacer);\n}\nbody > a[id=go-to-top]:before {\n display: var(--inline-block);\n content: var(--icon-increase);\n position: var(--absolute);\n left: var(--0-spacer);\n}\nbody > a[id=go-to-top]:after {\n display: var(--inline-block);\n content: var(--icon-increase);\n position: var(--absolute);\n right: var(--0-spacer);\n}\n\na:hover {\n cursor: var(--pointer);\n}\n\na {\n font-weight: var(--bold-font);\n text-decoration: var(--none);\n color: var(--key);\n transition: var(--transition-color);\n}\na:hover {\n color: var(--gray-darker);\n}\n\n[is=dateblock] {\n margin-bottom: var(--l-spacer);\n}\n[is=dateblock] p {\n margin: var(--0-spacer);\n font-size: var(--xs-font);\n}\n[is=dateblock] p > time {\n font-weight: var(--bold-font);\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n margin-top: var(--l-spacer);\n margin-bottom: var(--xs-spacer);\n font-family: var(--head-font);\n font-size: clamp(30pt, 9.375vw, 35pt);\n line-height: var(--l-line-height);\n}\nh1 + ol, h1 + ul, h1 + p,\nh2 + ol,\nh2 + ul,\nh2 + p,\nh3 + ol,\nh3 + ul,\nh3 + p,\nh4 + ol,\nh4 + ul,\nh4 + p,\nh5 + ol,\nh5 + ul,\nh5 + p,\nh6 + ol,\nh6 + ul,\nh6 + p {\n margin-top: var(--0-spacer);\n padding-top: var(--0-spacer);\n}\n\nh1 {\n margin-top: var(--m-spacer);\n}\n\nh2 {\n font-size: var(--xl-font);\n}\n\nh3,\nh4,\nh5,\nh6 {\n font-size: var(--l-font);\n text-align: var(--center);\n}\n\nhr {\n width: 33%;\n border: var(--2-px) var(--solid) var(--gray-darkest);\n border-radius: var(--1-px);\n}\n\ncode {\n font-family: var(--code-font);\n font-size: var(--s-font);\n}\n\n.toc {\n padding: var(--none);\n list-style: var(--none);\n}\n.toc li {\n border-bottom: var(--1-px) var(--solid) var(--gray-darker);\n}\n.toc li:last-child {\n border-bottom: var(--none);\n}\n.toc h3 {\n margin: var(--xs-spacer);\n text-align: var(--left);\n font-size: var(--l-font);\n}\n.toc h3 a {\n display: var(--block);\n padding: var(--m-spacer);\n}\n.toc h3 small {\n font-size: var(--s-font);\n display: var(--block);\n color: var(--gray-darker);\n font-weight: var(--light-font);\n}\n\nmain, article {\n padding: var(--0-spacer) var(--m-spacer);\n padding-top: var(--m-spacer);\n}\nmain img,\nmain iframe[src*=\"https://jsfiddle.net\"], article img,\narticle iframe[src*=\"https://jsfiddle.net\"] {\n display: var(--block);\n margin: var(--margin-centered);\n width: 80%;\n border: var(--3-px) var(--solid) var(--gray-darker);\n}\nmain iframe[src*=\"https://jsfiddle.net\"], article iframe[src*=\"https://jsfiddle.net\"] {\n min-height: 300px;\n}\nmain abbr, article abbr {\n text-decoration: var(--none);\n border-bottom: var(--1-px) var(--dashed) var(--gray-darker);\n}\nmain dl > dt, article dl > dt {\n margin-top: var(--m-spacer);\n}\nmain dl > dd, article dl > dd {\n margin-bottom: var(--s-spacer);\n}\nmain details, article details {\n border: var(--1-px) var(--solid) var(--key);\n border-radius: var(--3-px);\n margin-top: var(--m-spacer);\n padding: 0.5em 0.5em 0;\n font-weight: var(--bold-font);\n}\nmain details summary, article details summary {\n color: var(--key);\n cursor: var(--pointer);\n margin: -0.5em -0.5em 0;\n padding: 0.5em;\n}\n\nnav > ul {\n margin: var(--0-spacer);\n padding: var(--0-spacer);\n list-style: var(--none);\n}\nnav > ul > li:first-of-type {\n margin-bottom: var(--m-spacer);\n}\nnav > ul ul {\n list-style: var(--none);\n margin: var(--0-spacer) var(--0-spacer);\n padding: var(--0-spacer) var(--0-spacer);\n}\nnav > ul ul a {\n font-size: var(--s-font);\n}\nnav > ul ul li:last-of-type a {\n padding-bottom: var(--s-spacer);\n}\nnav > ul a {\n display: var(--block);\n overflow: var(--hidden);\n text-align: var(--center);\n position: var(--relative);\n}\nnav > ul abbr {\n border-color: var(--gray-lightest);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n html {\n scroll-behavior: var(--smooth);\n }\n\n nav > ul a:after {\n content: \"\";\n position: var(--absolute);\n width: 25%;\n border-top: var(--1-px) var(--solid) var(--gray-darker);\n opacity: 0.25;\n top: 60%;\n left: -100%;\n transition-delay: all 0.75s;\n transition: all 0.75s ease-out;\n }\n nav > ul a:hover:after {\n left: 100%;\n }\n}\n@media (prefers-color-scheme: dark) {\n body {\n background: var(--gray-darkest);\n color: var(--gray-lightest);\n font-weight: var(--medium-font);\n }\n\n hr {\n border: 1px solid var(--gray-lightest);\n }\n\n a {\n color: var(--key-light);\n font-weight: var(--x-bold-font);\n }\n a:hover {\n color: var(--gray-lightest);\n }\n\n [is=dateblock] p > time, b, strong {\n font-weight: var(--x-bold-font);\n }\n\n nav > ul a:after {\n border-top: var(--1-px) var(--solid) var(--gray-lightest);\n }\n\n main img,\nmain iframe[src*=\"https://jsfiddle.net\"], article img,\narticle iframe[src*=\"https://jsfiddle.net\"] {\n border: 3px solid var(--gray-lightest);\n }\n main details, article details {\n font-weight: var(--bold-font);\n }\n main details summary, article details summary {\n color: var(--key-light);\n }\n}\nblockquote {\n margin: var(--m-spacer);\n font-style: var(--italic);\n}\n\n.heading-permalink {\n display: var(--inline-block);\n margin-right: var(--2xs-spacer);\n}\n\na[rel~=noreferrer]:after {\n content: var(--icon-new-window);\n display: var(--inline-block);\n margin-left: var(--2xs-spacer);\n position: var(--relative);\n top: var(--icon-lift-spacer);\n font-size: var(--xs-font);\n}\n\nfooter {\n margin-top: var(--l-spacer);\n}\nfooter > p {\n text-align: var(--center);\n font-size: var(--xs-font);\n}\n\n/*# sourceMappingURL=file:///Users/alex/Public/joshbruce-repos/site-joshbruce.com/content/assets/sass/main.scss */",null,null]} \ No newline at end of file diff --git a/content/public/assets/favicons/android-chrome-192x192.png b/content/public/assets/favicons/android-chrome-192x192.png new file mode 100644 index 00000000..b0cf6262 Binary files /dev/null and b/content/public/assets/favicons/android-chrome-192x192.png differ diff --git a/content/public/assets/favicons/android-chrome-512x512.png b/content/public/assets/favicons/android-chrome-512x512.png new file mode 100644 index 00000000..59fcfd54 Binary files /dev/null and b/content/public/assets/favicons/android-chrome-512x512.png differ diff --git a/content/public/assets/favicons/apple-touch-icon.png b/content/public/assets/favicons/apple-touch-icon.png new file mode 100644 index 00000000..74f72ee9 Binary files /dev/null and b/content/public/assets/favicons/apple-touch-icon.png differ diff --git a/content/public/assets/favicons/favicon-16x16.png b/content/public/assets/favicons/favicon-16x16.png new file mode 100644 index 00000000..26c84693 Binary files /dev/null and b/content/public/assets/favicons/favicon-16x16.png differ diff --git a/content/public/assets/favicons/favicon-32x32.png b/content/public/assets/favicons/favicon-32x32.png new file mode 100644 index 00000000..bbb0aba4 Binary files /dev/null and b/content/public/assets/favicons/favicon-32x32.png differ diff --git a/content/public/assets/favicons/favicon.ico b/content/public/assets/favicons/favicon.ico new file mode 100644 index 00000000..7a22a602 Binary files /dev/null and b/content/public/assets/favicons/favicon.ico differ diff --git a/content/public/assets/favicons/site.webmanifest b/content/public/assets/favicons/site.webmanifest new file mode 100644 index 00000000..45dc8a20 --- /dev/null +++ b/content/public/assets/favicons/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/content/public/content.md b/content/public/content.md new file mode 100644 index 00000000..efa3ccc0 --- /dev/null +++ b/content/public/content.md @@ -0,0 +1,29 @@ +--- +title: Josh Bruce's personal site +header_quote: Welcome to the rabbit-hole. +copyright: Joshua C. Bruce +copyright_year: 2004 +--- + +Hello, + +My name is Josh Bruce (shocker). + +I've been creating content online since 1998 on various platforms. In 2020 I felt my content had become spread all over the place and I've decided to consolidate it here. + +Fair warning, I’m a multi-faceted being and will do my best to create logical separations and experiences for the various topics discussed. + +With each site iteration I try and push myself related to design, experience, and content. The big push into discomfort for me this time is in the realm of [finance](/finances) with other content coming back to life in this new home as the mood strikes me. + +I tend toward minimalism, which means I delete a lot more content than I keep. Until recently I didn't know a good way to handle this when creating content. I've decided to go with three types of posts: + +1. Evergreen: Entries created once and updated multiple times as information changes; the [investment policy](/finances/investment-policy), for example. These minimize the number of redundant entries and ensure the information you receive is up to date based on current knowledge and experience. +2. Serialized: Content ordered by date, each entry acting as a snapshot in time for a larger topic (a traditional blog); the [building wealth paycheck to paycheck](/finances/building-wealth-paycheck-to-paycheck) series, for example. +3. Ad-Hoc: Similar to serialized content in that it represents a snapshot in time; however, it may not represent a long running topic to warrant more than one entry. + +I hope you enjoy! + +Cheers,
+Josh + +ps. Some content may be flagged for creation but not yet created or moved. This is where you, the reader, can help prioritize the order content gets created and moved by providing feedback and creating "pull" for the creation of that content. diff --git a/content/public/design-your-life/_the-5-ps/content.md b/content/public/design-your-life/_the-5-ps/content.md new file mode 100644 index 00000000..27fe52f7 --- /dev/null +++ b/content/public/design-your-life/_the-5-ps/content.md @@ -0,0 +1,37 @@ +--- +title: The 5 Ps +dateblock: + - 20210623 Created on +--- + +# The 5 Ps exercise by Josh Bruce + +{!!dateblock!!} + +When [8fold](https://8fold.pro) considers bringing in a new Practitioner we go through this exercise with them before they participate in an 8fold event. + +Please list your top three-to-five in the following areas: + +1. Passions: Things you do without any more of coercion or motivation; the thing itself is the reward. +2. Pensives: Things you are willing to do but usually requires a bit of outside influence. +3. Prohibitions: Things you won't do regardless of coercion or outside influence. +4. Peeves: Things others do that bother you. +5. Pedagogies: Things you are wanting to learn more about or teach others about. + +What we often find is that many of us perform our "pensives" in order to afford our "passions"; a project manager who uses the money earned to pay for their music passion. Further, the pedagogies area is often used to increase knowledge and earning capability in the "pensives" area, while people tend to want teach others about their passions; paying for courses in project management to increase salary and pay for instruments that students can use when teaching them music. + +This exercise tends to be seen as more difficult the [Motivators exercise](/highest-version-of-the-self/motivators) because you're starting with a blank slate. However, when done together, we find the two are complimentary. + +Run through the motivators exercise, then look at your top motivator and list activities that demonstrate this in the "passions" bucket. For the "prohibitions" you may decide to list the inverse of the "passions." + +Another way to overcome the writer's block that can be associated with this is exercise is to treat it more like a brainstorming exercise. Don't limit your number of responses, just list everything that comes to mind and don't edit. Once you have a fair amount of items, review regular and iterate on them. + +For me, just off the top of my head and limiting myself to one each: + +1. Passions: Helping others make peace with space and time. +2. Pensives: Software development. +3. Prohibitions: Lying - specifically, speaking what I know to be counter to reality; I may be wrong about something, but it's because I'm coming from a place of ignorance. +4. Peeves: When people assume to know my "why" or speak on my behalf without my consent. +5. Pedagogies: Learn to become a better storyteller and teach others how to design their lives. + +If you look at my motivators, most of these should not come as a surprise, given my primary motivator is purpose and my second is typically autonomy. diff --git a/content/public/design-your-life/content.md b/content/public/design-your-life/content.md new file mode 100644 index 00000000..b9e49a62 --- /dev/null +++ b/content/public/design-your-life/content.md @@ -0,0 +1,45 @@ +--- +title: Design your life +dateblock: + - 20210619 Created on +--- + +# Design your life + +{!!dateblock!!} + +This is one of those "when I was a kid" stories, fair warning; I'll try to be brief. + +When I was 15 years old and for the rest of high school my house had become a gathering spot for my friends. We even called days where we got together "gatherings" and we pretty got together every day. A lot of times we just wanted a place where we felt safe to be ourselves. + +Most of the time we played games and shot the shit. + +It was hard to convince some of the parents of my friends that there wasn't anything more interesting happening. They couldn't believe that their kids would want to come to my house all the time unless there was sex, drugs, and rock and roll going on. Funny part was that I had three rules in my house: + +1. You can't drink alcohol. +2. You can't do drugs. +3. You can't have sex. + +Those were my rules, not my dad's. Basically, the only thing they were allowed to do at my house that they couldn't do at home was curse and smoke cigarettes, which many of them didn't even smoke. + +On the days when we weren't playing games and just chatting we would discuss existential issues. I know, 15, right? How deep could be really? + +To which I would like to turn to [David Bowie](https://www.azlyrics.com/lyrics/davidbowie/changes.html): + +> And these children that you spit on +> +> As they try to change their worlds +> +> Are immune to your consultations +> +> They're quite aware of what they're going through + +And, if you're not into that one, [Sophie Scholl](https://en.wikipedia.org/wiki/Sophie_Scholl) of the White Rose was 21 when she was executed for high treason against the Nazi Party; her brother was only 25. + +In fact, ageism was often a topic of discussion at these gatherings. + +Anyhoo. + +One of the things I started telling my friends was that I just wanted to help them become the highest version of themselves possible and hoping they would help me become a better version of myself as well. It would be a long time later before realizing it wasn't a Josh Bruce original. It would be even longer before I found I was able to essentially make a living at it. + +While I focus on helping people make peace with time and space it's really about helping you design your life. That's what this area of content is about. diff --git a/content/public/design-your-life/motivators/content.md b/content/public/design-your-life/motivators/content.md new file mode 100644 index 00000000..2d3485f7 --- /dev/null +++ b/content/public/design-your-life/motivators/content.md @@ -0,0 +1,68 @@ +--- +title: Motivators +dateblock: + - 20210620 Created on +coming-soon: +- review Getting Things Done by David Allen +- review Drive by Dan Pink +- review The 5 Love Languages by Gary Chapman +--- + +# Motivators + +{!!dateblock!!} + +If you haven't noticed I'm kinda big on values and principles as approach to automating decision making. When I first read Getting Things Done by David Allen I seem to recall feeling there was an idea that trying to identify values and principles was not seen as helpful while you're being overwhelmed; vacuuming while the house is on fire. + +There's definitely something to that. Over the years, however, I cannot tell you how many times the outputs of various exercises has helped me with decision making. As simple as which jobs to pursue to whether or not I should be upset about the latest sociopolitical craziness. + +The motivators exercise is inspired by Dan Pink's work from the book *Drive*, Gary Chapman with *The 5 Love Languages*, and some generation-based research. + +There's a short version and an extended version. Let's start with the short. You take a list of motivators and sort them in priority order for yourself from most motivating to least motivating. Here are the motivators: + +1. Mastery: Having a master-level understanding of the endeavors you pursue. +2. Autonomy: The freedom to choose your own adventure; how you work, when you work, what you work on, and so on. +3. Purpose: The reason or "why" behind things. +4. Connection: Relationships with others (*The 5 Love Languages*). +5. Process: The order and formal nature of things. +6. Sticks: Negative consequences for your actions. +7. Numbers: The facts and figures of things (includes money). +8. Prestige: The appreciation and recognition of others or a label that implies more responsibility or respect (office manager not secretary, custodial engineer not janitor, and so on). + +Now, just put them in order. Here's mine as of this writing (I review it about once a year): + +1. purpose, +2. autonomy, +3. mastery, +4. connection, +5. numbers, +6. process, +7. prestige, and +8. sticks. + +Here's the thing, the order just ensures that one thing is greater than the other. If you want, you can lay them out linearly on a scale to show the distance. For example, my top four are pretty close to each other. "Sticks" though has a lot of distance between it and prestige; almost to the point of being able to say I'm anti-stick. You wanna threaten to hit me with a stick? I'll probably let you just out of spite; check out the movie Good Will Hunting when they're talking about abusive parents. + +Anyway. Let's see what the expanded version looks like. Basically we're going to replace "connection" with each of the five love languages. My extended version looks like this: + +1. purpose, +2. autonomy, +3. **physical touch (I'm big on hugs and high-fives)**, +4. **words of affirmation**, +5. **quality time**, +6. mastery, +7. numbers, +8. **acts of service**, +9. **receiving gifts**, +10. process, +11. prestige, and +12. sticks. + +From this exercise I can start to get a sense of some basic values: purpose and autonomy, for example. This self-knowledge can help me reflect on practices and tools I use; are they in keeping with these values? + +I'm big on giving and receiving consent checks. That's in keeping with both purpose and autonomy as I need to explain my intent involving someone else (or myself) and the other person (or myself) has the freedom (autonomy) to decline or not participate. + +With this combination, I am toying with the principle: Only participate in that which is consensual. + +And this is how I find identifying your values, principles, practices (or habits), and tools goes. It's nonlinear. You don't need to figure out your life's purpose before determining your values before stating your principles and so on. Instead, you can start by cataloging your practices and asking yourself why that's a practice. + +This exercise is a way to get away from the writer's block that can often come from being asked to figure out what you want to be if you grow up. diff --git a/content/public/error-404.md b/content/public/error-404.md new file mode 100644 index 00000000..df5025b0 --- /dev/null +++ b/content/public/error-404.md @@ -0,0 +1,9 @@ +--- +title: Page not found +--- + +# Oh, no + +I'm sorry, it appears the content you are looking for doesn't exist. + +It's either been moved, deleted, never existed, or some combination. diff --git a/setup-errors/405.md b/content/public/error-405.md similarity index 100% rename from setup-errors/405.md rename to content/public/error-405.md diff --git a/content/public/error-500.html b/content/public/error-500.html new file mode 100644 index 00000000..26a63694 --- /dev/null +++ b/content/public/error-500.html @@ -0,0 +1,16 @@ + + + + Server error + + + +

500 : Internal server error

+

Not sure what happened here but weʼre pretty sure itʼs on us.

+

Please try again later.

+

If this error persists, please contact Josh Bruce. + + diff --git a/content/public/finances/_consolidated-blueprint/content.md b/content/public/finances/_consolidated-blueprint/content.md new file mode 100644 index 00000000..91690716 --- /dev/null +++ b/content/public/finances/_consolidated-blueprint/content.md @@ -0,0 +1,129 @@ +--- +title: Consolidated financial blueprint +dateblock: + - 20210511 Created on +--- + +# Consolidated financial blueprint + +{!!dateblock!!} + +The strange thing for me is that I didn't look into financial advice outside what I knew until I was debt free. I remember calling the institution that had my Roth [.IRA](Individual Retirement Account) and asking about planning for retirement. The representative said, "Get out of debt, then start thinking about that and call me." Seemed fair enough at the time and that's what I did. Probably not the wisest decision. + +We can't change the past, only determine how to progress into the future though, so, here we are. + +This is a consolidation of the advice I found useful over the years to get out of debt and accidentally create wealth, such as mine is. In other words, this isn't a review of the different, individual strategies but a holistic view and attempt at integrating them. + +With most of the strategies I've seen they are pretty generic and applicable to the most people. With that said, I think there would be benefit in adding nuance based on age range and what-if. The what-if being if I had made different choices. + +## Infrastructure (basic) + +This is about setup and constraints. + +At minimum you need someone willing to give you money, a place to put that money, and a way to take that money out and spend it. + +Habit 1: Always redistribute (or earmark) income. + +Stealing this one from teaching children about money. It's the three buckets: spend, save, give. You could have three literal buckets and if you get a dollar you would put a certain percentage into each of them. Maybe 50 cents in the spend bucket, 10 in the give bucket, and 40 in the save bucket. + +This takes that lump sum you received and divides it up into smaller portions. + +If you're under the age of 18 in the [.US](United States) you most likely can't open your own bank account, you would need to ask an adult to open what's called a custodian account for you. I'm not sure if some banks allow it at younger ages and with what types of constraints. But, you want to get a savings account as early as possible to facilitate habit 2. + +Habit 2: Maximize the amount of money making you more money; truly passive income. + +In fact, you should probably go ahead and open two savings accounts. One is for the save bucket and the other is for the give bucket. Putting this money somewhere that's not readily accessible is helpful and, if you're under the age of 18, you likely can't qualify for a checking account. (Also, if you're under the age of 18 and reading this, see the Terms of Service.) + +Let's shift to people over the age of 18; the same three buckets are a good starting place and it's advisable to add a couple more: + +1. Income account (new): This is a checking account at a financial institution; this facilitates depositing money you receive from others and makes the bookkeeping easier. +2. Save, Profit, or Hold (a name that implies you shouldn't spend this money): This is an interest-bearing account at a financial institution. +3. Taxes (new): This is an interest-bearing account at a financial institution (a different sub-account than the Save account). As your money makes more money, the government might want to take a portion of that money; it's referred to as taxable interest-income — this is where you'll prepare for that moment. +4. Giving: This is an interest-bearing account. This is a set aside for charitable donations and even gifts for friends and family. +5. Operating Expenses or Spend: This is a checking account and facilitates spending money. If you decide to get a debit card, this is the account to get the card associated with. + +Habit 3: Establish and maintain allocations and flows. + +Here's what I'm using as of this writing: + +1. All non-business income is deposited to the Income account. If you have a business and don't have a business account, I'd recommend opening a secondary sub-account specific to business revenue; helps with bookkeeping. +2. 10 percent goes to the Hold account. +3. 2 percent goes to the Taxes account. If you are self-employed, set this percentage to be the self-employment tax rate for your area and estimated revenue. +4. 0 percent goes to the Giving account. +5. 15 percent goes to the Operating Expenses account. + +We'll talk about the other 73 percent with the "advanced" version. Your percentages will vary based on life circumstances. If you're in six-figures of debt like I was, you might need to drop to 1 percent to the Hold account, for example. If you're just starting out with this type of setup, you may want to start with 1 percent to the Hold account and everything else to the Operating Expenses account. As you see how money flows in and out of the Operating Expenses account you may find you can increase the percentage to the Hold account (or other accounts you open, more on that later). + +Here's how money can flow between accounts: + +1. The Income account can transfer to all other accounts at the same institution. +2. The Hold account can transfer to the Operating Expenses and Giving account. +3. The Taxes account can transfer to the Operating Expenses and Hold account. +4. The Giving account can transfer to the Operating Expenses account. +5. The Operating Expenses account can only transfer out of the institution. + +Here are the events and under what circumstances these moves can be made: + +1. When income comes into the Income account, the money is distributed as soon as possible to the other accounts. +2. On a regular basis, usually once a quarter, a percentage (not all) of the money available in the Hold account is transferred to the Operating Expenses account, the Giving account, or both. +3. On a regular basis, usually once a year, a percentage of the money available in the Taxes account is transferred to the Hold account. If you owe taxes, transfer that amount to the Operating Expenses account and pay the taxes. If you don't owe taxes, no worries. Regardless, subtract any deposits made for the current calendar year, and transfer all or a percentage to the Hold account. +4. When you want to give to a charity or give someone a gift, transfer that from the Giving account to the Operating Expenses account. +5. Whenever a bill is due or you need money, use the Operating Expenses account. If there's not enough money in the Operating Expenses account, draw from the Hold account. If there's not enough money in the Hold account, draw from the Giving account. Don't withdraw from the Taxes account beyond the aforementioned tax season transfer. And if you need to draw from other accounts, consider adjusting your percentages. + +If you do nothing else, you'll be ready for what's coming and continuously preparing for the future. The nice part is that beyond the percentages all the other decisions can be automated because they've already been made. + +Habit 5: Increase income (financial and emotional), reduce operating expenses, and leverage the gap. + +## Practices + +These practices can be worked simultaneously by adjusting the percentages to focus on some more than others. With that said, these are in a recommended order of prioritization. + +Establish a minimum balance for your Hold account. Start at 1,000 USD (The Baby Steps: 1). Increase it to cover the maximum deductibles for all insurances (The Financial Order of Operations: 1); this could be on top of the 1,000 USD initially saved. When you take the redistributions discussed earlier, leave that amount in the Hold account. + +If your employer offers a 401(k), contribute to it. If the minimum payments on debts and recurring bills (subscriptions, rent, and so on) are greater than 30 percent of your monthly net income, start with 100 USD per paycheck and work on reducing operating expenses. If the minimum payments are less than 30 percent of your monthly net income, increase your contribution up to your employer match or three percent of your gross income if your employer doesn't offer a match. (The Financial Order of Operations: 2). + +Pay off high-interest debts. If you are under the age of 30, any debt with an interest rate greater than six percent. If you are under the age of 40, any debt with an interest rate greater than 5 percent. If you are under the age of 45, any debt with an interest rate greater than 4 percent. If you are over the age of 45, all debt. "Interest rate" includes emotional burden, which is a different type of interest (I paid my father back before my credit cards, for example). (The Financial Order of Operations: 3.) One approach is the snowball method (The Baby Steps: 2), which targets debts from the lowest to the highest balance; minimum payments to all, any extra money goes to the one with the lowest balance. Another approach is the debt avalanche, which tends to be more mathematically advantageous and may lack the psychological and emotional benefit of the debt snowball. I used a hybrid approach. + +Build a runway of one to 12 months of baseline living expenses; you always have enough to cover the current month. This can become the new minimum balance of your Hold account. I don't call this an emergency fund because, for me, that implies it can't be used unless it's an absolute emergency semantics (The Financial Order of Operations: 4). The idea with this fund is that you can access the funds, with minimal penalty, within a few days. How long of a runway really depends on how reliable your income is. My day job is as a contractor and never know when I'll be laid off. When I was laid off in 2018 I had about six months in my runway, I drastically changed my lifestyle, and took unemployment while I wrote my book (I was also still in debt). Now that I'm out of debt and pretty secure in my job, I'm willing to temporarily shorten the runway to increase my investments. + +Maximize tax-free contributions. In the US if an account type starts with "Roth," chances are you are contributing post-tax dollars, which then grow tax-free and you can withdraw from them without tax. For the Roth IRA, there's not many circumstances where it wouldn't be recommended. For the Roth IRA there are also income thresholds that could monkey with your plans (more on that later) and, as with all retirement accounts there's a maximum amount you can contribute each year. (Part of The Baby Steps: 4 and The Financial Order of Operations: 5.) + +Maximize pre-tax, tax-free contributions. The [.HSA](Health Savings Account) in the US is an interesting account because your contributions to it will either be pre-tax (reduces your taxable income) or you can deduct the contributions at the end of the year (reduces your tax liability at the end of the year). Not only that, you can withdraw the funds tax-free for medical expenses and, after a certain age, for pretty much anything. Further, if you keep the receipts for medical expenses and pay out of pocket for the expense, you can withdraw those funds at any time, even if the money isn't used to pay for that specific expense. One decision to make here are whether you are willing and able to accept a high-deductible health plan, which may require increasing the amount in your Hold account reserved for insurance deductibles. Another decision to make here is whether you will take the HSA provider selected by your employer, or choose your own. If you choose the one from your employer you may be limited in what you can invest the funds into and other considerations; however, the contributions will most likely be taken out of your check pre-tax, which immediately reduces your taxable income. If you choose to go with your own provider, you may be able to find investments you appreciate; however, your payroll department may not make the contributions pre-tax on your behalf, which means you'll need to take a deduction during tax season. (Part of The Baby Steps: 4 and The Financial Order of Operations: 5.) + +Maximize pre-tax contributions. At this point the water might be getting a bit more gray. Do you invest more in accounts that have age restrictions or not? If you're not looking to retire in the next 10 years, we're talking about your 401(k) and similar accounts. Start increasing the amount of your income that goes to these accounts. (Part of The Baby Steps: 4 and The Financial Order of Operations: 5.) + +Increase investment contributions to 15–25 percent or more of your gross income (The Baby Steps: 4 and The Financial Order of Operations: 7). You won't be able to do more to your employer-sponsored plans and IRAs at this point, so, this will most likely be in some form of a taxable account. We're looking for investments that return roughly 5–7 percent per year. + +Save for future known expenses and learning and growth. This could be for you or your children or both (The Baby Steps: 5 and The Financial Order of Operations: 8). Always make sure you're saving enough for retiring by 60; secure your mask before helping others. + +Pay off low-interest debt. See the "Pay of debts" paragraph, regardless of age. (The Baby Steps: 6 and The Financial Order of Operations: 9.) + +Increase giving. + +## Infrastructure (advanced) + +Wow, you're still here. + +We're going to add to our five accounts and some percentages, these are the percentages I use when I receive income, not necessarily recommendations: + +1. The Income account. +2. The Hold account; 13 percent. +3. The Taxes account; 2 percent. +4. The Giving account; 0 percent. +5. The Operating Expenses account; 15 percent. + +The "advanced" part of infrastructure is to take further advantage of the concept of fund accounting by opening more sub-accounts to help facilitate the practices. + +For me, that means adding a few more accounts: + +1. The Runway account: 10 percent. This is a money market account my primary financial institution and is for visualizing the runway amount you have and separating it from the Hold account balance. +2. The Invest Pass-through account: 60 percent. This is a checking account and is the account connected to my brokerages. That sounds so highbrow, like I'm some ultra-investor or something but it's because I have my preferred broker, which holds my main taxable account, my Traditional IRA, and my Roth IRA. They don't offer an HSA, so, I use a different company, which is the same company used by my current employer for my 401(k). Neither of them, to the best of my knowledge, have the ability to invest in the way platforms like M1 Finance do with partial shares and percentage-based portfolios with no commission fees. Anyway, the Invest Pass-through account allows me to continue keeping my Hold account separate from the money intended for investing. +3. The Equipment Pass-through account: 295 USD. This is an odd duck in comparison because it's not a percentage but a flat amount. I'm pretty dependent on technology to make a living. I want to make sure I can replace said technology on a regular basis; around five years. I have a set amount taken out of each of my paychecks and sent directly to another institution where I then transfer various amounts to certificates that allow continued deposits. + +You might have one you start to set aside, and keep separate, money you're saving up for a cruise or some other trip. + +## References + +- The Simple Path to Wealth by J.L. Collins +- The Financial Order of Operations by The Money Guy Show +- The Baby Steps by Ramsey Solutions diff --git a/content/public/finances/_health-insurance/content.md b/content/public/finances/_health-insurance/content.md new file mode 100644 index 00000000..e69de29b diff --git a/content/public/finances/_phone/content.md b/content/public/finances/_phone/content.md new file mode 100644 index 00000000..f3a2ce09 --- /dev/null +++ b/content/public/finances/_phone/content.md @@ -0,0 +1,4 @@ +--- +title: Phone Coverage +--- + diff --git a/content/public/finances/_time-oriented-budgeting/content.md b/content/public/finances/_time-oriented-budgeting/content.md new file mode 100644 index 00000000..537fa090 --- /dev/null +++ b/content/public/finances/_time-oriented-budgeting/content.md @@ -0,0 +1,57 @@ + + + + +Consider a water filter with multiple layers. The idea here is you dump water into the top of the filter and by the time it reaches the bottom it has had most, if not all, the impurities filtered out. + +The first layer consists of large rocks, which keep large debris from entering and clogging the lower levels only the bits that can fit in the spaces between the rocks make it to the next filter. The second layer is smaller rocks, which means smaller spaces for the water and impurities. The third is small pebbles. The fourth is sand. The fifth may be ground up charcoal. The sixth may be a tightly woven, layered fabric. + +Income is the water and the filters are groupings of similar places money can be distributed to. The goal at the end is to have roughly the same amount of money remaining in your spending account after each paycheck. + +This remaining amount is what I call the Minimum Comfortable Balance. This is the balance in your spending account your comfortable having from one paycheck to the next. For me it's about 100 [.USD](United States Dollars) plus or minus 50 USD; every other dollar should be working and adding value somewhere else. + +I use a spreadsheet to act as my filters and I start a new one each time I receive income (paycheck or otherwise). Each filter consists of a beginning balance, recommendations, and an ending balance, which becomes the beginning balance of the next filter. + +The first few rows (filter) are for capturing the current balance of the spending account, then add in all the incoming revenue and where that revenue is coming from. + +The second set of rows are for paying myself first. I want to save, somewhere, at least 10 percent of all income that comes from a third party. What this means is that money transferred from one account to another doesn't fall into this consideration (it moves between the rocks). This gives me a bare minimum amount to put into an interest bearing account. + +The third set of rows are bills that I know the balance owed on as of this paycheck, which may or may not be due before the next paycheck. Further, these tend to be bills I cannot pay with a credit card as I use credit cards as long as I can do so responsibly. My electric bill and rent usually end up here. + +The fourth set of rows are for revolving loans. A revolving loan is a shorthand way of saying money I have borrowed and once paid off can borrow again without additional paperwork; credit cards, lines of credit, and the like. The tricky bit with these is the minimum payment is usually 2 to 5 percent of the balance owed, which often equates to a 20 year loan if you don't borrow more, which is why I have a few different ways I like to pay these where the minimum payment is last resort (more later). + +The fifth set of rows are for terminal loans. A terminal loan has a term, it will terminate. Once the balance owed hits zero, if you want to borrow it again, you would need to sign new paperwork (refinance); mortgage, car loans, student loans, and similar. If you make the minimum payments each month, by the time you've paid the number of payments agreed to, the balance should be zero. In the beginning you will pay way more against interest than principle (this helps offset the risk of the lender should you fail to make payments in the future). I avoid terminal loans that have early payoff penalties and try to perform at least one extra minimum payment in the first month or year to add flexibility (more later). + +The sixth filter is for preparing for future known expenses. The idea here is to get the estimated cost of a future bill or purchase, divide it by the number of paychecks I will receive before that future bill or purchase is due, and stick that amount somewhere that accrues interest; a modification of the envelope method. 1,000 USD rent coming due in about 2 paychecks? Save 500 USD. 2,000 USD laptop in about 70 paychecks? Save 28.50 USD. This is what helps smooth the ride from one paycheck to the next; I don't have to think, "Oh! This is the first of month check, I need to do this whereas if this was the middle of the month check, I could do this" - just doing the same thing every time. + +The seventh filter is for cashflow - yes, in cash; I call this my cash allowance. I withdraw up to 200 USD from my spending account. Then, I use this 200 USD to by groceries, gas, going out to eat, anything that's immediate need and can be paid for in cash; I always favor spending cash before using credit cards. If I have cash left by the time my next paycheck comes around, then I'm doing good and will only withdraw the difference the next time around; letting the difference fall to the next filter. If I idon'thave cash left by my next paycheck, I don't beat myself too much and might consider upping the limit or use it as a reflection point to consider what other decisions I could have made. + +The eighth filter is the long-term savings; freedom fund. + +The ninth filter is the reality check where I have my minimum comfortable balance. I take the beginning balance and subtract the minimum comfortable balance, which leaves me a difference. If the difference is negative, I'm over my "budget" - no water remains after filtering and I need to make adjustments. If the difference is positive, I'm living below my means and can (should) do something with that money. If there is no difference, then my total lifestyle cost matches my means. + +There's usually three passes performed to scratch all the itches and make me feel comfortable with where I am. The first pass is inputting what I plan to spend and move money in the filters. The second pass is adjusting that initial plan. The third pass is checking off items as I do them. + +Takes roughly 10 minutes unless I'm doing something completely different. Once it's done, I don't think about it again until my next paycheck. And, the order of the filters creates a self-maintaining system where I don't feel restricted, or that I'm beating myself up, or becoming overwhelmed by historical spending; always be saving, always be paying down debt, short- and long-term goals are covered and the ride is smooth - as long as I'm getting consistent paychecks and have captured upcoming future bills. + +## Paying revolving loans + +When it comes to paying revolving loans I try to stick to this ordering and pay whatever is the highest amount: + +1. Pay the full amount owed (favoring the ones with the highest interest rate first). +2. Pay 2 to 5 percent of the limit (not the balance). +3. Pay the total of all debits made to the loan since the last time I paid, including accrued interest; this keeps the principle at the same amount month to month and can be helpful when using cards as the emergency fund in an emergency. +4. Pay the minimum payment on the statement (usually 2 to 5 percent of the balance owed). + +## Paying terminal loans + +When it comes to terminal loans I like to pay one extra payment early in that term; especially if the due date for the loan gets pushed out based on full payments made. If times were not good I probably wouldn't have taken on the terminal loan and when times are bad, having that due date be a month out helps me sleep. Further, paying one extra full payment at the beginning of the loan can reduce the payoff date if I make all the other payments on a regular basis (each paycheck for me, which can also reduce the payoff date more because I'm not waiting 30 days for the interest to accrue on a simple interest loan, which is what most terminal loans tend to be). + +Consider two 20 year, fixed rate mortgages for $100K. + +- Monthly, no initial payment: $554.60 per month for 20 years. +- Monthly, with one full payment extra in the beginning: $554.60 per month for 19 years and 11 months, saving roughly $450. +- Monthly, with one full payment extra at the beginning and an extra $50 per payment: $504.60 per month for 17 years and 9 months, saving roughly $4290.91. +- Biweekly, no initial payment: 18 years to payoff. + +For me, paying the little extra is more about pushing the due date forward than it is about paying the loan off early because my revenue and income tends to be more volatile than the average person. diff --git a/content/public/finances/budgeting/_2005-2021/content.md b/content/public/finances/budgeting/_2005-2021/content.md new file mode 100644 index 00000000..7b419eab --- /dev/null +++ b/content/public/finances/budgeting/_2005-2021/content.md @@ -0,0 +1 @@ +content.md diff --git a/content/public/finances/budgeting/content.md b/content/public/finances/budgeting/content.md new file mode 100644 index 00000000..91f7e2ee --- /dev/null +++ b/content/public/finances/budgeting/content.md @@ -0,0 +1,47 @@ +--- +title: Budgeting +dateblock: + - 20210704 Created on +coming-soon: +- review Profit First +--- + +# October 1st, 2021 paycheck + +{!!dateblock!!} + +I've always been torn on the word "budget." Mainly because I think the connotation of the word has overtaken the denotation (common use) of the word, which is: + +> an estimate of income and expenditure for a set period of time. + +That's not bad, right? + +Unfortunately, many think of budgeting in terms of another word whose connotation has given it a bad rap; "diet," which has two common usages (we're talking about the second when we talk in terms of a negative reputation): + +> 1. the kinds of food that a person, animal, or community habitually eats +> 2. a special course of food to which one restricts oneself, either to lose weight or for medical reasons + +Budget means restricting the spending, not estimating income and expenses in a given period. I don't do restriction well. Hell, I barely do delayed gratification well. + +Now, budget as a verb means to allow a certain amount of money to be allocated toward something in a budget (noun). + +Just like you have a diet (the food you habitually eat), you have a budget. The question here is how intentional is that budget (diet)? How mindful are you of that budget (diet)? + +The following describes my current method. + +## Traditional bank accounts + +I consolidated down from my previous method (that ran from around 2005 to 2021). I'm still rocking the same credit union I've had since around 1992. I'm essentially doing [fund accounting](https://en.wikipedia.org/wiki/Fund_accounting) and the method described in Profit First. + +There are six sub-accounts: + +- Income: A spending account (checking) where all money received from third-parties goes; this starts the flow before money is transferred to the other accounts. +- Profit: A savings account where a certain percentage of the money coming in goes; this is for emergencies and extended runway. +- Taxes: A savings account where a certain percentage of the money coming in goes; this is to prepare for possibly owing taxes each year. +- Operating Expenses: A spending account where a percentage of money goes; this is for paying bills. +- Runway: A savings account where a percentage of money goes; this is used to prepare for future expenses and emergencies. +- Investing Pass-through: A spending account where a percentage of money goes; this is used by brokerage accounts. + +Money deposited to the Income account is moved to the other accounts on the date the money is received to ensure the average daily balance of the other accounts (specifically the various savings accounts) is as high as possible for as long as possible; interest is based on the average daily balance. + +The Operating Expenses and Investing Pass-through accounts don't earn dividends, therefore, the balance will typically be held relatively close to a zero balance. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210301/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210301/content.md new file mode 100644 index 00000000..6341ac0a --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210301/content.md @@ -0,0 +1,74 @@ +--- +title: March 1st, 2021 paycheck +dateblock: + - 20210227 Created on +coming-soon: + - review The Simple Path to Wealth by J.L. Collins +--- + +# March 1st, 2021 paycheck + +{!!dateblock!!} + +Here's something I never thought I'd say: + +> I've adjusted my spreadsheet to allow me to track my finances based on [my investment policy](/finances/investment-policy) and easily share it here in the form of an image. + +Seriously, I cannot tell you how much I dislike spreadsheets. The spreadsheet I use to [build wealth living paycheck to paycheck](/finances/building-wealth-paycheck-to-paycheck) is the only one that's lasted longer than a month. + +I've just finished listening to The Simple Path to Wealth by J.L. Collins and I've made it up to Unit 7 of [Choose [.FI](financial independence)'s](/reviews-and-summaries/choose-fi) Financial Independence 101 course. + +I'm 41 and will be 42 in July, this is year one. + +I am debt-free. + +What that means for me is: + +1. I'm able to pay bills as they arrive (utilities and other non-credit payable bills), +2. I'm able to pay the balance of my revolving loans every paycheck (credit cards, lines of credit, and so on), and +3. I have no terminal loans (mortgages, car loans, student loans, and so on). + +The timing of when I get paid, when I move money, and when those movements actually settle might make things a bit out of sync; not sure how much that will matter. + +For example, as of me writing this, I had a pretty hefty balance on my credit cards, which I paid off. And, when I wrote this the money was “in transit.” Also, my brokerage accounts often get credited with a dollar amount before that amount is transferred from my account; makes it seem like I have more money than I really do because the same dollars appear in two places. We’ll figure out how to present it. + +When this paycheck landed I had a cash reserve of about four months of housing and food. My target right now is to basically have enough for the current month plus one-half or all of the next month. This will grow back up over time and I feel comfortable taking the risk in order to get the dollars into “the market” earlier. + +The cash reserve is currently held in these certificate-like vehicles at my credit union. I have one for each month of the year and they’re part of my more [time-oriented](/finances/time-oriented-budgeting) approach. There’s usually a penalty if I withdraw early. So, for now, this transition to the more lean approach will just happen naturally. + +Given this is the first year, I'm wanting to keep it simple and straightforward; minimal moving parts. + +I’m still learning how taxes work with the different types of investment accounts I now have. The [Choose FI Financial Independence 101 course](/reviews-and-summaries/choose-fi) also taught me something new; how progressive taxes work. I didn’t know that all your money wasn’t taxed at your marginal tax rate; the tax rate you qualify for based on income. Instead, there’s a thing called the effective tax rate. + +I make roughly 100,000 [.USD](United States Dollars) annually at the moment. This means: + +- 14,200 USD is taxed at 10 percent; 1,420 USD. +- 40,000 USD is taxed at 12 percent; 4,800 USD. +- 32,150 USD is taxed at 22 percent; 7,073 USD. +- What remains, ~14,000 USD for this example, is taxed at 24 percent; 3,360 USD. + +Then there's the standard deduction, which for me is 12,500 USD because I'm filing single; so, that 100,000 USD becomes 88,500 USD. At which point, only 2,150 USD is taxed at the 24 percent. Further, because I currently contribute six percent of my income to a pre-tax 401(k) plan, 6,000 USD, none of my income is taxed at 24 percent. If I up this contribution to the maximum, which is 19,500 USD, even less of my income will be taxed at 22 percent. Finally, if I actually max-out my [.HSA](Health Savings Account) contribution, which I thought I had been doing for the last two years, I can reduce that burden by another 3,600 USD. + +(In a way, I'm glad I wasn't contributing what I thought I was to the HSA because all that extra money went to paying off debt.) + +With that said, another freaking growth opportunity for me was realizing there are income maximums put in place to contribute to a Roth [.IRA](Individual Retirement Account). This limit is also based on what’s called your [.MAGI](Modified Adjusted Gross Income), which has to be calculated as it doesn’t appear as a line on your tax forms. If your MAGI is under a certain amount, you can contribute up to the max to the Roth IRA. If your MAGI is over a certain amount, you can’t contribute anything. If you MAGI is between those two, you have to do another calculation to figure how much you can contribute. Because we can’t make it simple. + +As if this writing, you need to take your gross income (all earned income). Then you subtract all pre-tax deductions; resulting in your [.AGI](Adjusted Gross Income). Then, you add back all or part of certain deductions. + +Luckily tax software should be able to calculate MAGI for you. + +TurboTax told me I would have been able to contribute the full amount for 2020, which means I should be able to contribute the full amount for 2021. I’ve contributed 96 percent already, leaving myself a bit of padding in case bonuses and mid-year raises change things enough to matter. And because my employer offers a retirement plan, I wouldn’t be able to take any deductions for contributing to a Traditional IRA. + +Seriously, this feels more complicated than it needs to be. We just keep adding vehicles and rules around those vehicles. I feel like the tax code is like listening to someone talk about Pokémon or Magic the Gathering. + +Anyway. + +I want to build up enough in taxable accounts to survive for 5 to 10 years, in case I FIRE at 50. I want enough built up in a Traditional IRA to cover another 5 to 10 years to carry me from 60 to 70. Then I have open access to all three. If I’m still working at 55 and decide to FIRE then, then I can withdraw from the 401(k) penalty free and earlier than I could from a Traditional IRA; you know, complicated and all time-based. Feel like Sir Ken Robinson talking about the most important thing being your date of manufacture. + +At 65 I should qualify for Medicare. And depending on if it’s still around and how I feel health wise, I’ll take social security around 65 as well. + +So, 50 is the taxable accounts. 55 any 401(k) funds I might have. 60 is any Traditional IRAs I might have. 65 is activation of government accounts. 70 is the Roth IRA and HSA accounts. + +I won’t be burning these accounts to zero between activations, that’s just when I’ll be able to access them. + +First things first though, cash down and stocks up; that's what we're looking at over the next few paychecks. I've been blessed with a big shovel and a low total lifestyle cost and I'm hoping it won't take long to get aligned. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210315/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210315/content.md new file mode 100644 index 00000000..edc12d4b --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210315/content.md @@ -0,0 +1,169 @@ +--- +title: March 15th, 2021 paycheck +dateblock: + - 20210312 Created on +--- + +# March 15th, 2021 paycheck + +{!!dateblock!!} + +Cash reserve is slowly going down as I'm using it for shelter and food while not continuing to save for future months; getting down to current and next month cash flowed. + +All the other major shifts I'm wanting to do are happening while the cash reserve is dwindling. + +Let's talk about those major shifts; seems relevant. + +## Financial institutions + +I have primary, secondary, and tertiary institutions identified. These are "traditional" institutions — mostly credit unions; I really like cooperative and member-owned business models. I've had two of the institutions for over 20 years now. + +### Primary + +Their standard savings account is paying around 6 percent on the first 500 [.USD](United States Dollars) as long as you have an "active checking account". An active checking account means either a direct deposit coming in *or* a checking account with 4 or more transactions per month. I qualify under both. + +(Side note: Some payroll departments will let you split your direct deposits between multiple institutions; so, I could have an active checking account even if this wasn't my primary institution.) + +They also offer these sub-accounts that have nice features I haven't found at other institutions. + +1. I can make deposits to them regularly. +2. I can't withdraw the money without a penalty. +3. They pay dividends; albeit small, but still. +4. I choose when the funds will be rolled into my checking account. +5. As long as there's a positive balance in the sub-account within 30 days, I don't need to create another account (like I would with a traditional certificate situation). + +I have 12 of these and one rolls over at the beginning of each month. This affords me the ability to perform the envelope method while a) not using physical envelopes and cash, b) earn dividends for saving up for known future expenses, and c) not have to manually perform the transfer. + +This institution has my second longest running, lowest rate credit card, which has helped me establish an exceptionally long credit history (more on that later). + +I also have a line of credit for overdraft protection on the checking; using credit cards can get a bit sketchy when used for overdraft protection. + +### Secondary + +This institution was a pleasant surprise as I was planning on having them as the tertiary institution for business accounts. Their customer service and products had me shifting that mindset within the first week. + +This institution will be replacing a local credit union currently housing my business accounts. + +### Tertiary + +This institution I mainly use for my auto and property insurance but they do offer other products. + +My mother's annuity I set up for her was through them and didn't come with the pitfalls I've heard mentioned by others. + +## Credit cards + +My life used to be easy when it came to credit cards. The shifts I'm going to be making has caused this to change. + +I have one card with my primary institution. I've had it for almost 20 years. No changes planned here. It's a low rate card at about 6 percent and is used if I think I'll carry a balance. + +I have another card with my new secondary institution I opened recently. It's a rewards card giving 3 cents for groceries, transit (anything but an airplane), and restaurants; with 1 cent on everything else regardless of category. I put together a spreadsheet based on my credit card purchases in 2020 and found that those three categories are the bulk of my purchases; would have earned about 400 USD. I'm hoping to make this my new primary card. If I don't it will be used with those categories at minimum. + +My third card used to be my primary card. It's a rewards card and earned me about half what the previously mentioned card would have. I've had it for 20 years. The plan here is to hold onto this card while the previous and next card age. Then I'll close it down and the history should remain on my credit report for 10 years according to Experian. + +My fourth card will replace the previous card from the same institution. It's a straight cash rewards card and earns 1.5 cents on the dollar regardless of category. I'll use it for subscriptions and instances where AMEX isn't accepted. + +My fifth and sixth cards are both business cards; one for each business. If the accounts at the secondary institution get opened, I'll try to get cards through the secondary institution and close these two out, which consolidates my institutions back to the three mentioned. + +## Retirement accounts + +I have a 401(k) with my employer. It's 100 percent vested. It doesn't offer my desired index funds but I'm faking it by having a small-, mid-, and [.S&P 500](Standards and Poors 500) index fund. The changes I made was going from the target date fund into the S&P 500. Then, during open enrollment, I opened the other two and have my contributions going to them on a 50-50 split and nothing more going to the S&P 500 to get the equities asset allocation described by the [Investment Policy](/finances/investment-policy). I increased the contributions by 2 percent, going from 5 to 7 percent; employer match is on the first 5. (I got a raise and am hoping to reduce my tax liability without blocking myself from a sizable amount in my non-retirement accounts.) + +I decided to go [Health Savings](https://healthsavings.com) instead of the HSA provided by my employer; I've been burned in the past with an HSA being tied to my employer and don't want to deal with that again. I decided to use them because they partnered with Vanguard and have the funds I'm interested in. They're customer service doesn't suck. I'm not going to max it out at the beginning of the year as my health insurance situation can change and it's my understanding the contribution limit is tied to a monthly construct...yeah, not sure how to word that better, that's where it lives now. + +Decided to shift to maxing out the Roth IRA at the beginning of each year; leaving room to deal with the income caps and whatnot...I feel weird having to say this is a concern in my life. + +If there comes a point where I can't max out the contributions to the Roth IRA, I'm hoping to open a traditional IRA at that time or have opened one by rolling over my 401(k). I'm hoping that by keeping my contributions increasing along with any salary increases that I will always be able to max out the Roth though. + +## Taxable accounts + +I have the Vanguard index funds doing their things. I have an M1 Finance pie doing its thing (made 20 cents in dividends already). + +The funds in the taxable accounts, including those at the traditional institutions, is what I'm planning to carry me from 50 to 60 when the IRAs become available. Of course, this presumes I don't maintain active employment during that time. I do enjoy what I do for a living. + +## Medical expenses + +When it comes to financial independence (for the short period I've known about its existence) in the United States, medical expenses before qualifying for Medicare seems to be *the* question. + +Near as I can tell, the situation depends on your current medical condition, perceived future medical needs, and your needs for the past 12 months or so. + +Let's start there. + +I don't go to the doctor outside regular checks. I've never broken a bone, I've had one cavity so far, and had to get stitches when I was 10. With that said, when I go to the hospital, I *go* to the hospital. + +I've been to the hospital 5 times in my 41 years on this planet. Each time it was roughly 7 days. All those were in the last 15 years. + +Here's a story indicative of me going to the doctor. + +I used to live in Washington DC. I was riding a bike to work. I hit a curb next to the Jefferson Memorial and flew Superman-style over the handlebars. + +All that went through my head was, "Protect the brains." + +Yes, plural. The meat-brain in my skull and the messenger bag I affectionately refer to as my brain. + +I must have looked like I was parachuting from a plane because I bent my body back as much as I could, landing on my chest then sliding about three feet on my side. (Did I mention I wasn't wearing a helmet?) + +Now, the concrete outside the memorial is probably not what you think of when you think concrete sidewalk. It's basically cement mixed with jagged pebbles about the size of an adult thumb. + +I was tore up. + +It looked like I lost a fight with a cheese grater. + +The palms of my hands were scraped to hell. The left side of my torso caught the next worst of it. And, then there was my left elbow, which was draining blood all over the place. + +A bunch of runners came to my aid. One of them tied my elbow off with a clean, black sock he had to change into when he got to his office (at least that's what he told me). We called the woman I was dating at the time. She came and picked me up to take me to the hospital. + +They looked me over. Said they couldn't do anything for me and told me to put bandages on my side and palms. When I asked about the elbow, the doctor looked at it and said, "Well, I can't even stitch that, there's just nothing there to stitch." + +A piece of the jagged rocks embedded in the cement had carved out a chunk of my skin, which I apparently left at the memorial. So, it's not a cut or a gash. The skin was gone and they couldn't pull the sides together and stitch them. + +We went to the pharmacy and picked up creams and ointments to treat cuts along with a mess-ton of bandages. + +I called my manager who insisted I come in the next day. I did. + +I showed everyone the pictures. And my palms (I was doing software development at the time and leaked blood on my desk a couple of times). I was there a few hours before being sent home. + +A couple weeks later I was doing pretty well. The hole in my elbow had formed what I heard referred to as a blood plug, which is what it sounds like, a plug made of coagulated blood. It was stopping the hole from healing (closing) quickly. So, I pulled the one inch, solidified blood from my elbow and bandaged the hole. In a week, it was healed, now there's barely a noticeable scar. + +A day or two later, the woman I was dating came over with a bicycle helmet. + +That's my normal hospital visit. + +"Please, help." + +"There's nothing we can do for you." + +I lost a toenail once and had concerns about how it was growing back; nothing. I jumped a bench and caught my foot and went to a podiatrist who took X-rays and MRI scans; nothing. Chest pain once; nothing. Was deaf in my right ear because of a buildup of earwax, they did try I will grant them, then gave up and sent me home; I went to the pharmacy and bought a syringe and read all about removing wax buildup in ears before making that part of my monthly routine (and stopped using cotton swabs to clean my ears). + +So, this isn't some macho masculinity trip, I go to the doctor if something seems off. They just usually send me home saying there's nothing they can do for me. When they don't send me home, I stay in the hospital for a week while they figure out how to help me. + +All right. + +All that to say, that's my baseline. + +My projection is that I'm 41 now and hoping to be financially independent enough to retire at 50 if I want to. Which means I need a medical solution that'll carry me from 50 to 65, if nothing changes in the United States regarding health care. + +I want to have the benefits of an HSA; so, no matter what, I'll be carrying some type of high deductible health plan until I'm 50 or stabilized it enough that it's not losing money from fees (again, that's why I went with Health Savings as the provider, very reasonable on the fees and not tied to my employer, which loses some tax benefits; worth it, I think). The health plan provided by my employer is about 200 USD per month. I could've signed up for one through the [.ACA](Affordable Care Act) marketplace, which would have further decoupled me from my employer and cost around 350 USD per month. + +I reached out to an ACA advisor provided by my now tertiary institution. They explained how the plans were pretty much locked to the state or even county I lived in. So, if I traveled a lot or for work and something happened, the hospital I went to would stabilize me and then send me back to the location my ACA plan was tied to. + +Put another way, and after asking the question straight up, I was told that: My health plan would either be nationwide coverage tied to my employer or limited coverage tied to my state of domicile. + +I asked how nomads (RVers) did it. The advisor didn't know. I got off the phone and started searching. I found a company that specializes in providing medical insurance plans to full-time nomads. + +In short it's multiple insurances. The keystone insurance is an indemnity plan; depending on the procedure, I get a check for a certain amount of money and pay the rest. At which point it's not the same as a health share, because there are no deductible-like expenses, just the premium; however, it is like a health share in that I pay in and can use them to help with medical expenses. I also get to say "I'm self-insured and would like your best cash rate, please." + +It's around 300 USD per month. + +I qualified mainly because I am not currently being treated for something and I haven't been treated for anything in the last 12 months; 2020 was good for something, I guess. + +Now that I'm qualified as long as I continue paying the premium, they can't deny me for medical procedures covered. It's also a nationwide Preferred Provider Organization. + +Not anchored to my employer. Not anchored to my domicile location. + +While the HSA is stabilizing I'll have both coverages at roughly 500 USD per month and, once stabilized, that'll drop to roughly 300 USD per month regardless. + +I can't double dip though. Therefore, this will give me a chance to see what happens. + +My understanding is that I can go to the doctor. Do the self-insured thing. Once I get all the bills, I can submit them to the indemnity insurance company I'm using. They could deny it for some reason. At which point I can pass it to my employer's health insurance provider (the devil I know, so to speak). If I have to submit it to my employer's health insurance, then I'll likely cancel the auxiliary coverage and keep the employer-provided or grab an ACA plan. + +If it works, and the HSA is stable, I'll cancel the employer-provided or ACA plan. At a 3600 USD maximum contribution per year to the HSA, we have some time (thinking two or three years at the most). diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210401/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210401/content.md new file mode 100644 index 00000000..7269cd18 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210401/content.md @@ -0,0 +1,135 @@ +--- +title: April 1st, 2021 paycheck +dateblock: + - 20210326 Created on + - 20210331 Updated on +--- + +# April 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Two main things remain regarding major changes to how I do the money thing. + +The first is the great credit card migration, more on that later. + +The second is continuing to consolidate and otherwise reduce future expenses, which includes an annual membership due to an association I joined six years ago; 80 [.USD](United States Dollar) per year. + +I'm not much of a joiner and always want to get a lay of the land before committing. I ask myself if I want to be associated with these people; particularly the worst among them because that's how in- and out-group preference and bias works. I haven't decided if I want to be associated with the people in the group; however, I haven't run away yet and there does seem to be decent membership benefits, which I would like to incorporate into my Financial Independence lifestyle moving forward. + +They offer a lifetime membership. The price goes down with age. For me it was a little over 1,000 USD. When I posted to the [Choose FI](https://www.choosefi.com) Facebook group that I had done this lifetime membership, it caused a bit of a stir. The majority of people (over 200) reacted with likes or hearts or similar. Then there were two groups of commenters. + +One group didn't like that I had used the word "saved" in the context of no longer spending money. We had an interesting conversation that ended with me conceding that, yes, strictly speaking, I'm no longer spending that money on that thing. However, that means 80 USD per month needs to go somewhere and no matter how we cut it, at least 30 of it will go toward long-term saving. + +It’s not the whole 80 because I joined a different association, the [Escapees RV Club](https://www.escapees.com). This membership is 50 per year. I'm looking at the benefits and forums to check out the community. Given this is the desired future lifestyle the membership seemed a fair investment. So, this first year will be to look at, and take advantage of as many of the benefits as I can; then decide if I’ll pause the membership until I'm ready to hit the road. (They offer a lifetime membership and I’d need to live for 17 more years to break even.) + +The other group of commenters from the Facebook group brought up the very real risk the association would be gone before I break even (in roughly 17 to 22 years, I'm hoping to live at least twice that long and if I don't, it won't matter if I spent this money). I did put the risk of them going away into my considerations. + +The association has been around almost 100 years. They seem financially stable enough to hold events and do the federated organization thing. The association is more about community than it is about a product or technology, which is where I see people "getting burned" most often. Lodges, POW-MIA associations, and things like the Masons, for example, have been around for ages and will probably continue to be around, because it's about community more than it’s about a gadget or process. + +So, let's see if I can break this down: + +1. I canceled my carshare membership, which was 70 USD per year (wasn't near any of the cars and wasn't using it because of COVID). +2. Canceled my extra cloud storage, which was about 120 USD per year. +2. I purchased a lifetime membership to this association, which was 80 USD per year (I get the benefits without the cost until death do us part — mine or theirs). +3. I switched to a prepaid cellphone plan, making my bill go from around 1,440 to roughly 360 USD per year. +4. I added 50 USD per year for the Escapees RV Club. +5. I added 3,600 USD per year for the auxiliary medical insurance. + +All told, a net loss. I saved (or cut, if you don't like the word saved being used this way) 1,450. I added 3,650. Meaning I spend 2,200 more dollars per year than I did last year. The way I look at the insurance and membership I added is I can cancel them at any time and am looking to see what the full benefits are given their purposes. + +If the insurance can cover medical expenses for me, then I can get rid of my employer plan, which saves me 2,400 USD per year; this would mean losing the tax benefit and ability to contribute to my HSA. It also means my medical care would be decoupled from my employer and geographical location. + +## Financial institutions + +Cash reserves going down. + +Waiting to hear back on moving my business accounts to a new institution. + +I’m hoping to have 3 institutions for personal, business, and insurance. Right now I technically have 4. + +## Credit cards + +Received the American Express card. If I spend 3,000 USD on the card in the first 90 days I get bonus points with a 250 cash value. + +I put the association membership on the card, which got me almost halfway there. I’ll be using it for the first couple months of premiums for the insurance, which should get me two-thirds there. I had a 300 USD medical bill for a tele-health call I did after a fall. I’ll move other monthly expenses to it temporarily and see what happens; the key is to buy things I prepared for anyway, not just shop to get points. It's also getting used to replace some of the clothes. + +I discovered Instacart comes across as general retail, not supermarket or grocery, which means I’m not getting the 3 cents per dollar I was going for. With my latest order, I went with the grocery store’s app. They use Instacart for the delivery and hopefully it comes through as grocery or supermarket. (It doesn’t come through as a supermarket purchase; so, hopefully I can start going to the grocery like I used to. I’m appreciating Shipt over Instacart.) + +I’m using grocery delivery because of COVID and I can afford to delegate that and over-tip the drivers. Going myself after getting vaccinated should lower monthly expenses (hoping to get vaccinated April 2nd). + +Received my new cash rewards card. This becomes my card for recurring payments and fallback if a place doesn’t accept American Express; 1.5 cents per dollar regardless of category. + +This leaves a third card, which is currently my primary card. The limits for the two new cards combined is 13,000 USD. To maintain the same available credit, I’ve decided to reduce the limit of my current primary card to 2,000 USD. My utilization rate is effectively zero, so, not worried about negative impacts to my credit score due to the reduction. This card is one of the 20 year old cards and I’ll probably keep it open for a couple of years while the other two begin to mature. [Experian](https://www.experian.com/blogs/ask-experian/will-closing-a-credit-card-hurt-your-credit/) says the credit history for the card will be around for 10 years after I close it; until then, the card goes on a shelf. It’s open and has a credit of almost 100 USD and some points — they’re sending me a check. + +I have another card that’s been open for a while. I don’t plan on making any changes to it. + +Once the business accounts are moved, I’ll try moving the credit cards over to the new institution. Still can’t get the current business cards added to Apple Wallet on my phone. + +## Retirement accounts + +Received a letter in the mail from a bank I’d never heard of. It had instructions on how to sign up for an account. Beyond that the letter had the bare minimum information stating its purpose, namely: You had a 401(k) with a balance less than five thousand USD somewhere, we bought it, we tried contacting you about it and the mail was returned, please update your mailing address with us. + +Three things hit me upon reading this: + +1. Weirdest spin on the Nigerian scam ever. +2. There is a distinct possibility this is legit (I blame [RIP Medical Debt](https://secure.qgiv.com/event/choosefi/). +3. You have my address, unless you sent these to all Joshua Bruce’s and are waiting for a call. + +But my brain’s in a money collecting headspace and I decided to chase it a little. + +I opened a private browsing window and went to the site. Seemed legit (given my history in web development). I wasn’t about to register an account though. + +I called the number on the letter instead. + +This means they’d have my phone number, which wouldn’t be too difficult to find if they had my address. One of the first things the automated receptionist asked for was my full social security number. + +“Fuck that,” I thought to myself and started pressing zero, hoping to bypass the automated service and get a human; if that didn’t work, I woulda bailed. + +I got through to a person. + +I explained I’d never heard of them, didn’t remember signing up for a 401(k), and wasn’t about to give them my full social security number. I also mentioned my confusion at being asked to update my address given they obviously had it. + +The representative explained he wouldn’t ask for my social or personal information and instead asked me to read a unique ID in the top corner of the letter. Since they generated that number, I figured why not. + +They said I had a balance of about 1,000 USD and asked which employer it might be from. I mentioned a couple of my previous employers and they said sometimes employers enroll you regardless (you have to opt-out not opt-in). The agent called out the name of a former employer I didn’t mention, which helped increase my trust. + +In short order we pulled the history together and I’d received two emails (once I trusted them). I called Vanguard. + +I decided to go ahead and open my Traditional IRA with this money. The money was already in a Traditional IRA invested in a regular savings account at 0.15 percent and being charged a 15 USD administration fee. I filled out the paperwork and sent it to Vanguard to process. It can take up to six weeks. + +A “transfer in kind” would take the money from the institution and put it in the same thing at Vanguard. This is what we did with my Roth IRA. We apparently can’t do that this time. It has to be “sold” then transferred and deposited into the settlement account for the Traditional IRA. Once it’s settled, I’ll buy into the [.ETF](Exchange-Traded Fund) equivalent of VEXAX. ETF because I don’t have enough to get the Admiral Share and because the IRAs are almost 20 years away from being used. + +So, we’ll see how this goes. Until then it appears my plan of waiting to open a Traditional IRA until becoming separated from my employer and rolling over my 401(k) might not need to be. + +Also discovered I had 50 USD in an HSA that was slowly getting feed into oblivion; I thought it had already been feed into closure. Started the paperwork to transfer the balance to Health Savings. The HSA holder wanted me to print and then mail or fax the documents. I haven’t owned a printer since 2005, a scanner since 2007, and have never owned a fax machine. I called Health Savings. They asked if I could email the documents to them and they’d take care of it. I did. Now we wait. + +## Taxable accounts + +I check my portfolio multiple times a day right now. I'm taking my cue from my meditation practice instead of the advice of others who say, "Just don't pay attention and check on it in 10 or 20 years with a doctor on standby." + +In my meditation practice, which I'm perpetually performing, I sit with my emotions and responses to things. I don't push away or edit, just sit. Same thing here. + +The first month, the final tally for my portfolio was negative 200 USD. The second month, my final tally was negative 54 USD. This month though, I watched as the value soared upwards of 700 on one day and then negative 300, then next morning, after settling while the markets were closed it went to negative 165. Then, over the weekend it was positive 75. + +My mentality when it went up was, "Hey, good thing I bought in already." When it went to negative 300 I was like, "Too bad I don't have anything to throw in there right now." + +Therefore, at this point, I can say, it shouldn't be difficult for me to stick to the [investment policy](/finances/investment-policy). Once I have more in, it might become more difficult and that's why I will be checking in with myself regularly. + +I did have a moment where I questioned whether to max out the IRA contributions at the beginning of the year or dollar cost average more. Given the ship has sailed for this year, I’ll sit with it and see how the rest of the year unfolds. + +I should mention, most of my money in the taxable brokerage account is in VEXAX, so, minor shifts in the value of VEXAX are amplified. VTSAX is acting as ballast; negative 100 the first month, negative 20 the second, and positive 50 as of this writing. + +## Medical expenses + +I scheduled an appointment with the CVS Minute Clinic to get tested for COVID antibodies. I decided to call the medical insurance company first to see what coverage I would have if I used them. + +Their website had a COVID [.FAQ](frequently asked questions) page, with a number to call if I had questions. Making it easy to talk to a human is a plus in my book. + +I was put on hold though, which is a bummer. + +35 minutes later...I needed to prepare for a meeting and hung up. + +Given the timing and circumstance, it's understandable that there would be longer than normal wait times. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210415/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210415/content.md new file mode 100644 index 00000000..8528ff32 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210415/content.md @@ -0,0 +1,76 @@ +--- +title: April 15th, 2021 paycheck +dateblock: + - 20210331 Created on + - 20210510 Updated on +--- + +# April 15th, 2021 paycheck + +{!!dateblock!!} + +Thinking things will be settling down as the new rhythm is established. + +I’m looking into accounting more. I had an accountant who was wonderful, she said she was retiring and I should find someone else. I signed up with a different accountant who I felt didn’t communicate well with me. That’s when I decided I needed to get educated to do the accounting and taxes “well enough” on my own. + +I’m using [Wave](https://www.waveapps.com) for most of my bookkeeping. They have coaches for a one-time set up session that I’ll take advantage of. I’m looking to do consultation training sessions with H&R Block, also partnered with Wave. We’ll see how that goes. + +COVID quarantine has messed up my clothe replacement cadence and I’m finding I need to replace in bulk whereas I normally stagger and spread; replacing one piece here and another piece there. One pair of jeans gets worn out, buy new pair...a while later another pair gets worn out. Now it's like all my jeans are worn out, most of my shirts, and none of my shoes. I don’t like buying jeans if I can’t try them on, especially since I’m getting smaller after switching my diet and starting to take the stairs more. + +## Financial institutions + +Cash reserves going down. + +Still waiting to hear back on moving my business accounts to a new institution. + +## Credit cards + +Still have 6 cards. 2 business and 4 personal (2 I use regularly). None carry a balance. + +Oddly enough, the story of money coming back to me continues. + +I was refunded for a charge I put on one of the cards for business expenses (state documentation). Come to find out the documentation was already filed and the company I use to file all that for me refunded me the bill. Granted I had already cashed in the reward points when I put the credit into one of my checking accounts; I'm negative 361 points at the moment. However, I'll be using the cash from that refund to make the bulk of my credit card payment when I get my next paycheck. + +After figuring how to pay back the reward points that were already cashed out; if that's even a thing. (I called and learned that all my reward points go into the same bucket regardless of card of origin, so, they don’t care it’s negative.) + +## Retirement accounts + +This was my time to think about legacy. In short, I see three primary legacies human beings leave: + +1. genetic (have kids, very common), +2. financial (leave money to someone or something), and +3. intellectual (capture and spread ideas). + +Most people will emphasize one of these while toning down the others. I’m opting out of the first and emphasizing the other two. Retirement is the beginning of a financial legacy, past-me to future-me. + +This led me down the trust account rabbit hole. + +You might be thinking, “But you don’t have kids.” And I hear you. That was the first question I had and found out you can list whoever you want as the beneficiary of a trust; even businesses and charities. You can also create multiple trusts. + +For example, Benjamin Franklin opened two trusts. The beneficiaries of the trusts were the city of Boston and the city of Philadelphia. 1,000 USD over 200 years to see compounding in action. The ability to make withdrawals twice. Once after the first hundred years, leaving some in the trust, and the second withdrawal after another 100 years. + +Once the trust account is (or accounts are) set up, you can list the trust as the beneficiary on all your other accounts, which consolidates the money and other assets into the trust, avoids probate court, and attempts to keep the matter private; whereas wills become part of the public record. Anyway, once the assets are moved to the trust, the trustee executes the terms established by the grantor of the trust. + +That was the other interesting thing; trusts aren’t limited to money. You can have artwork in there. A record player. Almost anything. + +I'm working on increasing my knowledge here and have a lot of ideas whirling around regarding a trust, a foundation, an association; therefore, we'll leave it at that. + +With that said, I went ahead and decided to up my 401(k) contributions to 10 percent. I want to get in touch with my benefits department to see if there's a way to add a couple of index funds to the pool we can choose from, which would reduce my expense ratios. They won't let me do a dollar amount as a contribution, only a percentage. Given my living expenses are low, I could contribute the full 19,500 USD per year. I want to see if I can front-load that at the beginning of the year. + +Let's take 2021 as an example. With my first check in January I want to start maximizing the Roth IRA contribution; leaving myself a bit of padding. Once I get all the tax forms and have a feel for my modified adjusted gross income, I want to top-off what I can in the Roth IRA; if there's anything left to contribute for 2020, I'll put it in the Traditional IRA. + +With the IRA as topped off as it can be for 2020 and 2021, I want to maximize the 401(k), if it doesn't reduce my total employer contribution. I was talking with an agent for my 401(k) who mentioned that if I contributed everything at the beginning of the year, I might lose out on some of the employer match. I'm not sure how that works mathematically and will contact my benefits department. Regardless, it would be a lot easier for me to throw in the whole thing at the front of the year because I have my salary, bonuses that contribute to the 401(k), and the possibility of raises. + +Assuming that contributing 19,500 USD always results in the same employer match, then at this point I will have my IRA and 401(k) topped off, which leaves me able to contribute to the taxable accounts and HSA throughout the year. (There is a tax question in here I'll explore, if it seems like an option.) + +## Taxable accounts + +I'm modifying the M1 Finance experimentation as I learn more about how that tool works. + +I'm doing the nested pie thing. Basically, I have a pie with one slice at 100 percent. It contains one equity. That pie becomes a slice in another pie representing the brands I'm using or appreciating for that quarter in the given year. + +The M1 Finance portfolio is mainly for insurance deductibles per the [Financial Order of Operations](https://www.moneyguy.com/resources/) from The Money Guy Show. So, it's not "serious money" I'm depending on for retirement and it'd be nice if it at least maintained value against inflation and spit off decent dividends on occasion. + +## Medical expenses + +Nothing new here. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210501/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210501/content.md new file mode 100644 index 00000000..29040292 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210501/content.md @@ -0,0 +1,131 @@ +--- +title: May 1st, 2021 paycheck +dateblock: + - 20210501 Created on +--- + +# October 1st, 2021 paycheck + +{!!dateblock!!} + +I thought things would have settled down — I was wrong. + +The [.US](United States) Federal Government extended the tax filing deadline, which extended how long I could contribute to my Roth [.IRA](Individual Retirement Account) for 2020. So, the cash reserves I have will be going to try and maximize that contribution. + +I had a coaching session with a [Wave Advisor](https://www.waveapps.com/wave-advisors) to improve and verify my bookkeeping skills. Come to find out I'm doing pretty okay on that score and they were able to help me with a few interactions and capabilities of the app. + +I was introduced to a book and labels I didn't know existed. + +For the last 15 years I've been using a [time-based](/finances/time-oriented-budgeting) approach that mimics the envelope method. I had my trusty spreadsheet. Income would come in. I'd account for paying myself first. Next I pay bills that can't be paid with credit cards; rent, for example. Then the focus was on the revolving loans (credit cards and lines of credit), followed by any terminal loans (student loans and car loans). Then there was a section for preparing for future, known expenses. A cash allowance I gave myself came next. Then there was long-term investing and savings. + +I loved this spreadsheet. + +It helped me lay out a plan before committing to it. Then, once I had the plan in place (after about 10 minutes), I'd transfer the money and make all the payments. Then I wouldn't have to look again until the next time I ran the spreadsheet (on the first and fifteenth of each month). Now, I'm pretty sure I can get rid of, or at least dramatically change the spreadsheet's position in my financial execution. + +The first mind-altering concept I was introduced to was that of [Fund Accounting](https://en.wikipedia.org/wiki/Fund_accounting). In short, Fund Accounting is used by not-for-profit organizations and is the corporate version of the envelope method used at the personal level. You create funds, each fund and the money within it have stipulations and constraints for what that money can be used for. The US Federal Government, as a not-for-profit organization, works this way. Social Security is a fund. Money is put in and stipulated to be used to pay those receiving the benefit. Of course, using it for the purposes intended is up to the trustee of the fund. In the case of personal accounting, that's you. + +This eventually led to the book [Profit First](https://profitfirstbook.com/). This method uses sub-accounts at your financial institution to create funds. The the funds recommended by the book are: + +1. Income, +2. Profit, +3. Taxes, and +4. Operating Expenses. + +The author, Mike, does say you should have a mirror for the Profit and Taxes accounts at another institution as these are reserve accounts and it should help dissuade you from withdrawing money on a whim. The idea is that all money received from third-party people starts in the Income account. Then you have percentages set to redistribute the money from that account to the others. Mike talks about how this methodology aligns with [Generally Accepted Accounting Principles](https://www.investopedia.com/terms/g/gaap.asp) and for-profit business bookkeeping, and how this can be used at the personal level. + +Here's the implementation I decided to shift to. + +1. Income account: I opened another checking account and updated my payroll department to have my paychecks go here; I have a bit go to my secondary institution as well (more on that later). +2. Hold (this is the Profit account from the book): I renamed the savings account I have with my primary institution. +3. Taxes: I opened a secondary savings account. +4. Operating Expenses: I renamed my original checking account at my primary institution. +5. Runway: I renamed my money market account at my primary institution. +6. Investment Pass-Through: I opened another checking account. + +This is important: Each of these are sub-accounts. So, when I sign in to my primary institution, I see all six of these sub-accounts. They are not unique accounts at the institution. (It's similar to a brokerage account at, say, Vanguard; each "fund" is contained inside the "account.") + +For this run, I closed all my time-based accounts (those savings accounts meant to be used around the first of each month) and moved their balances to the Income account. Then, leaving around one thousand [.USD](United States Dollars) in the Income account, I transferred the money to other accounts: + +1. 10 percent to Hold. +2. 2 percent to Taxes. +3. Enough to pay credit card balances and bills that were due to the Operating Expenses account. +4. Nothing to the Runway account. +5. The rest went to the Investment Pass-Through account. + +Let's talk about the accounts. + +The Investment Pass-Through account is the account connected with the companies I use for investments; think Fidelity, Vanguard, M1 Finance, and the like. This way it doesn't get mixed up with my Operating Expenses fund, which has a different purpose. It also helps separate income received from third-parties from dividends and income generated by investments. + +The Income account is where all payments from third-parties come into my world. If they use a similar system, their money would flow from their Operating Expenses account and into my Income account. + +The Operating Expenses account is used to pay third-parties. It's not a transfer from one asset account to another, it's a transfer out of my pocket and becomes the income for someone else. + +The Runway account is an extension of the Operating Expenses account. It's an interest-bearing account; money making money. + +The Hold account is the "pay yourself first" account and is interest-beating. + +The Taxes account is the "pay the government" account and is interest-bearing. + +A couple of notes before talking about how money flows and why. + +First, discussing the difference between time- and flow-oriented approaches in-depth is beyond the scope of these entries. + +Second, because I'm more hands-on with this approach seeing where and how operating expenses can be reduced should be easier and I should be able to do it without having to rely on the tracking spreadsheet. + +Third, I added a summary for how each account operates to the description of the account in the Wave chart of accounts. This further reduces the need for the spreadsheet while increasing the possibility of bringing in a third-party to manage this for me or at least see into it; thinking accountant or bookkeeper. + +Finally, instead of using a separate sheet (tab) in Apple Numbers for each paycheck, I've consolidated everything into two sheets. One stores the past distributions and the other holds the current and future distributions on a rolling 12 month cycle, with two runs each (24 columns in total). + +## Event-based distributions + +### Incoming! + +All right, let's say this check was for 1,000 USD. It got deposited to the Income account, that's the event. + +I took 10 percent (100 USD) and put that in the Hold account. I took 2 percent (20 USD) and put that in the Taxes account. I paid my bills due and paid off my credit cards and transferred that amount to the Operating Expense account. I transferred the rest to the Investment Pass-Through account and told my brokerage firms to take out the various amounts ear-marked for them. + +I didn't apply all the percentages this time because I'm trying to draw down my cash reserves and feel I have the risk tolerance and capacity to do so pretty quickly. + +### Dividends earned + +When it comes to passive income, dividends are king of the hill. Put money somewhere and more money gets generated; as long as you don't spend the money that's in there. + +For some accounts (401(k), IRAs, and the like) dividends don't count as taxable income. For other accounts, they do. In the US, if you earned more than 10 USD in taxable interest income from an institution, you'll receive a 1099 form letting you know. And, the government may take a cut from that money. + +That's why the Tax account is important. + +My primary income comes from W2 employment, which means they already take out various money for taxes and pay them on my behalf. Awfully kind of them and I do what I can to make sure I don't owe money or receive money when I file taxes. If it swings more than 300 USD in either direction, something has gone awry. + +That's why I put the 2 percent into the Taxes account every time I run through this paycheck protocol. That's also why I'll either put in an amount equal to my taxable interest income earned since the last time I ran through this or I will literally transfer the dividends from one account to the Taxes account. + +### Quarterly redistributions + +After a while, the Hold account should start accumulating a pretty healthy balance. On the first day of each fiscal quarter (January first, April first, and so on), I plan to take 50 percent of the available balance and either redistribute it — most likely to the Runway account, the Investing Pass-through account, or a little bit to both. + +We'll find out what I decide at least initially when the June first distribution happens. + +### Annual redistributions + +The Taxes account needs to have a balance to handle taxes for the year. If I owe taxes when I file, I'll transfer the amount I owe from the Taxes account to the Operating Expenses account and pay the taxes. Then, because I want those dollars working as hard as they can for me, I will make a transfer from the Taxes account to the Hold account. + +I'm thinking I'll do something like this: + +1. Transfer taxes owed to the Operating Expenses account. +2. Subtract any deposits made since January of that year from the Tax account balance. +3. Then transfer 80 percent of that figure to the Hold account. + +This way the money doesn't disappear immediately from my cash accounts and is fed back into the pipeline to be promoted or at least put to better use by way of the quarterly redistributions. + +### Overdrafts + +Something comes out of the Operating Expense account unexpectedly. + +There are a lot of options here and I'm going to describe them in general and which ones I have. + +Institution default: In some cases institutions will *not* pay the item that came in that would have drawn the account negative. There's usually a fee from the institution if that happens. This is the deep fallback option. + +Line of credit: I have a personal line of credit with my primary institution. This is a revolving loan that will be advanced automatically if something comes in that would have drawn my account negative. Some institutions let you use a credit card for this, I avoid that option. The reason I avoid it is because overdrafts are considered a cash advance. Cash advances are often charged daily interest instead of monthly (similar to the line of credit); however, when you make payments, the payment will sometimes go to pay down purchases before going to pay off cash advances (not the same as the line of credit as there's only one type of charge there). + +Another account: Another option, which I'm setting up is to have the funds drawn from another account automatically; specifically the Runway account. To be clear, there will be a chain here; like an overdraft conga line. If the Operating Expense account is about to go negative, money should get transferred from the line of credit. If the line of credit is maxed out, funds should be drawn from the Runway account. If funds aren't available in the Runway account, funds should come from the Hold account. The idea is to set up the distribution and redistribution ratios in a way that this never happens. + +At no point should funds be taken from the Taxes account. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210515/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210515/content.md new file mode 100644 index 00000000..949c2806 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210515/content.md @@ -0,0 +1,148 @@ +--- +title: May 15th, 2021 paycheck +dateblock: + - 20210515 Created on +--- + +# May 15th, 2021 paycheck + +{!!dateblock!!} + +Think I've finally settled on a graphic for this series. It's a simple line chart and took me a moment to iterate to this point. + +From about 2006 until 2020 I had been using the same [time-oriented](/finances/time-oriented-budgeting) approach. While I appreciated that approach for getting out of debt, I found it didn't serve me in where I was going after getting back to broke. + +I'm a proponent of [zero-based budgeting](https://www.investopedia.com/terms/z/zbb.asp) even before I knew what it was called or that it was formally labeled something. I'm also a proponent of the [envelope budgeting system](https://en.wikipedia.org/wiki/Envelope_system), which I didn't know had a name beyond what my high school personal finance teacher told us. I tend to equate the envelope system to [Fund Accounting](https://en.wikipedia.org/wiki/Fund_accountingf), which is used by not-for-profit organizations (including the US Federal Government). Each fund is essentially an envelope and every dollar that comes in is shifted or earmarked for one of those envelopes, leaving a zero-balance for the Income account. + +Anyhoo. + +Once I found the formal implementations of these concepts I abandoned the version I was coming to by bumping into walls and iterating slowly and grabbed on like Captain America holding onto a helicopter. + +I wanted to stop using the spreadsheet altogether. And, after two or three paychecks, I think I might be there. + +I recently started using [Wave](https://www.waveapps.com) as my accounting software for both [8fold](https://8fold.pro) and my personal finances. I'm appreciating the interface and experience; however, it's an account aggregator and falls victim to the pitfalls of most financial software that syncs with various accounts. Specifically: + +- The list of transactions may be out of date. +- You need to sign back in to re-sync the transactions. +- You will either want to use it as an after-the-fact tool, or a pre-emptive tool (more on that in a moment). +- There's also some accessibility concerns I have from a development perspective. + +Let's focus on the after-the-fact or pre-emptive tool. + +Using accounting software as an after-the-fact tool means you aren't using that software to plan for the future, you're aggregating historical data, verifying that data, and generating reports. Which, for me, meant I needed a tool to handle the present and future — that's the spreadsheet I'm trying to get away from. + +Using accounting software as a pre-emptive tool on the other hand means using it to input transactions almost as soon as they happen. This means the tool has a more accurate representation of reality than your financial institution; the old-school check register. Of course, this means you probably don't want to sync your accounts because if you're entering transactions regularly you'll end up with duplicates when the transaction hits your financial institution and, eventually, the accounting software after it syncs again. + +Let's get away from my description and to a case study. + +I went to the grocery store the other day. + +I paid for the groceries using Apple Pay with my card that gives me 3 percent cash back on supermarket purchases. The receipt printed. I launched the [Wave Receipts app](https://www.waveapps.com/receipts/) and snapped a picture of the receipt. It did the automatic processing to figure out merchant and amount. I updated the information with which account I used to pay and which category the transaction should use. Then I told Wave to add the transaction to my ledger and it did; thereby, updating my balances inside Wave. + +It would take one or two days for the credit card to convert the pending charge to an actual charge instead of a pending transaction. It would then take Wave another day or two to sync and show the transaction on the ledger for that card. And, when Wave did sync the transaction to catch up with what I already did, there's a possibility there would be a duplicate entry I would need to deal with...meaning I'm no longer able to trust the accuracy of the system. + +Luckily, or unluckily, depending on how you want to look at it, Wave doesn't have the ability to automatically sync with all my accounts. + +They use [Plaid](https://plaid.com) to access the other financial institutions. Near as I can tell, they use the basic plan that doesn't allow for syncing with investment accounts — the language of support folks tends to feel like throwing Plaid under the bus, which bothers me — they are your partner. + +In other words, they don't say, "Wave is testing Plaid out at the moment. In an effort to reduce costs and continue passing those savings to our users, we have opted for the Plaid plan that doesn't include investments. That may change in the future." Something like that would be better than, "Our third-party account aggregator doesn't allow us to do that" — like Plaid is incapable of it. This bus throwing language gets users complaining about Wave's choice to partner with Plaid, at which point Wave does come to defend Plaid in comparison to their previous aggregator. Anyway, Plaid apparently does have the ability to sync with investment accounts, just not on the plan most likely used by Wave. + +My turn to throw Plaid under that bus a bit, because they don't seem to be able to show the line of credit I use for overdraft protection. Messaging from Wave throws Plaid under that bus as well: Our data provider is unable to supply transaction data for most types of loan accounts. (With a link to [this page](https://support.waveapps.com/hc/en-us/articles/360031202851-What-types-of-bank-accounts-can-I-connect) for details.) + +Anyway! + +I have a lot of accounts I want to track inside of Wave for personal use. Most of them can't be synced automatically and I'm not sure I would want to even if they could; specifically: + +- Apple Cash and Card (which is new) can only be viewed in the Wallet app on my device. +- I have three different brokerage providers at the moment each with one to three sub-accounts; can't sync any of them (not a shot at Plaid, a soft shot at Wave). +- I'm tracking those investment accounts at the fund-level, which means each sub-account may have two or more funds. + +In short, there are around 33 accounts inside the Wave chart of accounts I'm tracking for personal finances. 14 of those can be synced automatically, if I'm okay with them being up to 5 days behind real-time; that's one benefit to being cash-based. + +What I decided to do instead is have Wave sync 4 of my credit cards and the Income Fund; at least for now. I'll do the other 29 accounts manually. + +That might seem like a lot of work; however, the credit cards are the most active accounts and I only use 3 of the 5 for most things. When I use them directly, I can use Wave Receipts in the moment. Otherwise, they'll be charged online and I may not do a manual entry at that point...that's why they still sync automatically. + +By the time the first and fifteenth roll around there shouldn't be that much work to do — I hope — and if it seems like too much we'll just iterate again. I'm hoping that by using Wave Receipts I can avoid the duplicate entry problem as well. + +Wow. That got long. + +All that to say, I'm going to try and use Wave as a pre-emptive tool. + +Instead of going to the spreadsheet to plan how I'm going to move money from the Income fund to the others, I will go to Wave. I'll review transactions and whatnot to make sure I'm caught up and the books seem correct compared to reality. Then I'll create new transactions for transfers to various accounts and bill payments. + +That becomes the plan. Until the transactions are actually processed and settled, I will leave the transactions unreviewed. + +Once I have the plan in place, I'll make the money moves. When the transactions become processed and settled I'll mark them as reviewed; most likely the next time I go through the process. + +This replaces the bulk of what the spreadsheet did for me and does it in more detail while being able to generate reports and whatnot. + +I still have a spreadsheet. The spreadsheet is basically to generate the asset allocation graphic. + +## The plan + +For the 401(k) I have 11 percent of my pre-tax income going there at present, which I'm going to increase throughout the year as I dial in what I need to land in the Income fund to support my total lifestyle cost. I updated my payroll allocation to have a specified amount go toward replacing my equipment (computers and phones), this is taken out before distributing from the Income fund. From the Income fund: + +- 10 percent to the Profit fund. +- 2 percent to the Tax-hold fund. +- 15 percent to the Operating Expense fund. +- 10 percent to the Runway fund. And +- 60 percent to the Investing Pass-through fund. + +That leaves 3 percent inside the Income fund. Because this is a drastically new way of doing things for me I am being a little conservative at the tail-end of reducing my liquid cash reserves. Basically, that 3 percent is the last of it. Going forward, I'll be taking the Income fund to a zero-balance every time. + +The next paycheck will be on June first, which means I'll be shifting the money from the Profit fund and potentially changing these flow percentages. + +As more money flows into the small- and mid-cap US equities, the ratio of the large-cap and municipal bonds should drop in comparison, which is what I'm going for. Also, as the allocation percentages shift, my cash reserves will continue to build back up. + +With that said, I want to be running pretty lean while I'm earning an income from a day-job. That means trying to reduce the percentage required for the Operating Expense fund from each paycheck to increase the amount going to the Investing Pass-through fund. I'm considering, or hoping, to reduce the percentage put into the Profit and Tax-hold funds, which will allow me to increase the amount held back for the 401(k) and put toward the Investing Pass-through fund. + +I'm thinking of switching from buying frozen, steam-in-the-bag vegetables and go with fresh. I started using frozen vegetables because sometimes I wouldn't cook at home and the fresh vegetables would go to waste. Now that COVID has calmed down and I feel comfortable shopping along with reducing operating expenses by not eating out as much, I think buying fresh will have multiple benefits: + +1. should be less expensive, +2. should more nutritionally rich (I won't be cooking or microwaving them), +3. should be in stock more consistently (sometimes the frozen vegetables I eat are out or too low to cover the week — everybody seems to always have broccoli and carrots), and +4. the desire to avoid waste tied to a minimal prep-time should inspire me to eat from what I have more. + +The timing of my paychecks, these posts, and the snapshot are throwing off my flow. Here's what I'm thinking: + +1. I'll do the snapshot on the first or fifteenth. +2. I'll make the moves a day or two prior; I get paid biweekly and not on the literal first and fifteenth — though I do like the idea of getting back to moving money on or near when I get paid — not waiting. + +This way the snapshot will be somewhat settled based on the current balances of the accounts. + +## The odd + +The doubt settled in a bit with this paycheck. It was interesting. + +[Personal Capital](https://www.personalcapital.com/?expVar=control) generates this thing they call the You Index®. It measures daily changes in percent of your synced portfolio. You can compare it against the blended approach recommended by Personal Capital, the [.S&P 500](Standards and Poor’s 500), and others. Up until this paycheck, I would compare and the difference never seemed like a lot; usually marginally behind against the S&P 500 and Total Stock and ahead on the foreign and the blended. This time, there have been multiple times where it's been behind all of them. + +It’s an interesting feeling. + +I’m watching my portfolio get out performed by almost every alternative except bonds. And there’s that feeling of doubt. That needle prick of “I should change something.” + +Then a voice in my head said, “Stop comparing it to other things, judge it on its own merit” — my inner voice is either a punk or a philosophy professor. + +One interesting part is that based on backtesting the portfolio, I know when it drops, it will typically drop lower than the total stock market; it's a byproduct of the even market-cap distribution. Yet, here I am, doubting the design. + +Another interesting part is I didn't create the portfolio based solely or primarily on performance — a fool's errand, right? If the principle is that the average person (me included) won't out perform the market on a regular basis, why is performance suddenly feeling like a painful thing? (Told you checking daily was a meditative exercise.) But, no, I based the portfolio and my [investment policy](/finances/investment-policy) on values and principles I hold. + +Anyway. + +I checked balances to see how that would go. The almost 700 [.USD](United States Dollars) gain from April is back down to 100 USD. So, technically, checking balances didn’t go well. Of course, I’m not looking to withdraw and have no idea why it seems to be bothering me. + +Then I looked at the investment chart from Personal Capital. The downward slope is almost unnoticeable, especially compared to what happened around this time in April. + +Having just purchased more VEXAX to finish off the 2020 Roth IRA contributions, I decided to check prices; what had I paid for it. 134 USD. Put in context, the cheapest I‘ve bought in at is 131 USD and the most expensive was 141 USD. The median price is 137 USD and the average deviation as of this writing is around 3.50 USD. This means I got in at a discount, which made me feel a bit less troubled. + +That’s another thing. Intellectually I know that fluctuations in the value of VEXAX will be amplified because I have more of it. Further, I’m not losing shares in the fund, it’s that their perceived value is less. Like losing buying power with cash, which is roughly 3 percent per year. + +Then there’s the time scale. This is a 10 year plan. I’ve been operating the plan for less than six months. The strategy isn’t even based on numbers, again, it’s based on my values and principles. On a 10 year time scale, most of the money will be from direct contributions, not returns. + +And yet, here I am battling with doubt. Asking myself the question, should I do something different? Put more money into a different index fund. Even stranger is the slight underpinning of asking whether I *should have* done something different; like regret. + +A popular refrain with me is: You can’t change the past, only determine how to progress into the future. + +Then there are the principles I hold, specifically “past performance doesn’t guarantee future performance.” My approach isn’t grounded in performance, it’s grounded in my values and principles; I keep coming back to this. And, if I do change something it should be grounded in the same manner, which means changing my values and principles or at least reframing them. + +Odd emotions seeping into the brain. Walking up a mountain playing with a yo-yo is a metaphor used by [The Money Guy Show](https://www.moneyguy.com/) and I think it's an apt metaphor. And, for me right now, it feels more like walking up a mountain while *riding* the yo-yo. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210601/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210601/content.md new file mode 100644 index 00000000..c87f311b --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210601/content.md @@ -0,0 +1,66 @@ +--- +title: June 1, 2021 paycheck +dateblock: + - 20210601 Created on +data: +- [Debt, 0, 0, 0.05] +- [Cash, 2, 4, 8.08] +- [Invest, 0, 0, 0] +- [US Bonds - munis, 1, 2, 4.6] +- ["US Bonds - [.Gov't](Government)", 0, 0.5, 0.01] +- [US equities - small, 30, 35, 17.52] +- [US equities - mid, 30, 35, 22.5] +- [US equities - large, 30, 35, 45.98] +- [Int. bonds, 0, 0, 0] +- [Int. equities, 0, 2, 1.22] +--- + +# June 1, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +I increased the contribution to my 401(k) by 2 percent; 13 percent for this paycheck. + +Received a bonus this pay period. This is also supposed to be the paycheck where I take money from the Profit account and split it to other accounts. + +In other words, this is a super payday. + +For the sake of transparency, and in case I haven’t mentioned already, I actually get paid biweekly; not on the first and fifteenth. I try to only make the moves and payments in a way that they settle by the first and fifteenth. For all paychecks I’ll have the weekend to develop a plan before money will actually move. For most paychecks, I’ll have about a week to settle into a plan. + +I’ve been doing some math to verify being able to reach the FI number in 10 years. I need 250 [.USD](United States Dollars) per pay for the Roth IRA, 150 USD for the [.HSA](Health Savings Account), and at least 871 USD to my taxable accounts. That’s 1,271 USD in total. I’ve already contributed what I can to the Roth for 2020 and 2021. 722 USD per, biweekly paycheck to the 401(k) would get the maximum annual employee contribution and employer match. + +Having the hard minimum dollar amounts should help make informed, percent-based decisions given my current context. + +If the percentages don't hit the numbers, I know I should adjust. If the percentages overshoot the numbers, then I know I have some flexibility. When I feel income-stable and feel my risk tolerance and capacity is high, I’ll probably increase what gets sent to investments; otherwise, I’ll probably send more to the Runway account. + +Right now my current gig isn’t feeling very stable with me in it and the [.US](United States) stock market is fluctuating particularly in the [information technology](https://finance.yahoo.com/news/investors-eye-inflation-seasonally-weaker-101005191.html?j=987392&sfmc_sub=466063440&l=83_HTML&u=24848245&mid=7295435&jb=5527&utm_source=sfmc&utm_term=fleeing&utm_content=482468&utm_id=263dbc5d-d4c4-40d3-8d3c-4f4683ab546b&sfmc_activityid=31da9b20-5814-4030-906f-b042b02817a7&utm_medium=email&utm_campaign=MM_5.21.21) sector. + +The asset mix of my portfolio, according to [Personal Capital](https://www.personalcapital.com), is about 25 percent information technology. According to Vanguard by itself, I’m 22 percent, which is 1 percent less than the overall stock market. Of course, this is happenstance as my asset mix is based on equal [market cap](/finances/investment-policy) distribution. Also-also, I actually haven’t achieved my desired mix in the portfolio overall and the Vanguard numbers are probably a better representation of the target than the Personal Capital numbers at present. + +With that in mind, I will be making sure to move 1,271 USD to the Invest account, at minimum. + +Let’s start with the quarterly distribution from the Profit account. Take the current available balance in the Profit account and reduce it by 500 USD. Then I’ll transfer half of that. 2 percent to the Tax account and the rest to the Invest account. (If 100 was available to transfer, 2 USD to the Tax account and the other 98 to the Invest account.) + +On to the Income account. 2 percent to the Profit account. 2 percent to the Tax account. 15 percent to the Operating Expense account. 15 percent to the Runway account, this time; normally would be 11 percent. + +Honestly I don’t think the 15 and 11 percent will be enough to continue. [Wave](https://www.waveapps.com) tells me just over 50 percent of my income is going to expenses at the moment. Part of that is because of larger purchases being made from the built up cash. Part of the tighter income is also in saving for a new desktop to be purchased this year. I’m debating on purchasing the desktop with the bonus received from this paycheck; if nothing else, I will probably move more money to the preparation account. + +The above represents the immediate, back-of-the-envelope plan; I’m going to leave it there for now as we continue ironing out how these entries and their content will work. This also means I typically move as much as I can from non-interest-bearing accounts as soon as I can while maybe waiting a bit to pay bills and credit cards. This increases my average daily balances as soon as possible while minimizing money going out to third-parties until the last responsible moment. + +I’m going to need enough in the Operating Expense account to pay the balances of my credit cards. + +I’m going to take a little less than half from the Income account and move it to the Operating Expense account; using the Income account to pay the credit cards instead of pulling from the Runway account as designed. Another little less than half will go to the equipment account, which should increase the dividends to be paid out when it matures; have to write a check because the dollar amount is too much to do in one shot any other way. The rest will go to the Invest account to wind up in VTSAX. + +I’m still not sure I’ll go for the desktop computer; however, I’m leaning very heavily in getting rid of my television and streaming device regardless. I’ll replace both with my laptop and monitor. The other benefit to having the desktop (all-in-one) is it can check my email and apply email rules immediately. Applying email rules has not been added to the iPhone. + +(The desktop will check mail every minute and apply the rules. The laptop will check every 5 minutes. The phone only checks when I launch the app - saves data.) + +Also, given this is the beginning of a quarter, I should receive a fair amount of dividends that will adjust balances. I won’t take the snapshot until the first of June and I’ll need to make sure the balance of the tax account is greater than 30 percent of the year-to-date taxable interest income amount. + +Given this round, I think the percentages will be changed to 2 percent to the Profit and Tax accounts, 23 percent to the Operating Expenses and Runway accounts, and 50 percent to the Invest account. + +The goal being to have enough to invest 1,271 USD per paycheck at minimum. That means a net deposit to the Income account of 2,542 USD. As long as the amount deposited to the Income account is greater than that and the amount I contribute to my 401(k) is less than 722 USD, then I can keep increasing the contribution to my 401(k). If the balance of my Runway account decreases over time, I’ll reduce the amount going to the Profit account and try to reduce operating expenses; increasing how much goes to the Runway account. + +Part of the bonus I received this round seems to have pulled into the 401(k) contribution at the same percentage level. That means the total contribution this time is larger than the 722 USD target. My total contributions for 2021 is much less than 9,250 USD (half the maximum contribution). My thought is I will go ahead and up the contribution by another 2 percent. This will probably drop the net deposit to the Income account for the next paycheck to less than 2,542 USD. My hope is I'll be able to put the 1,271 USD into the Invest account. The next paycheck should be the last time I'll be doing the rapid preparation for the iMac before shifting around 225 USD back to my regular paycheck distribution. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210615/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210615/content.md new file mode 100644 index 00000000..d39c5c68 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210615/content.md @@ -0,0 +1,90 @@ +--- +title: June 15th, 2021 paycheck +dateblock: + - 20210604 Created on +--- + +# June 15th, 2021 paycheck + +{!!dateblock!!} + +I increased the contribution to my 401(k) by 2 percent; 15 percent for this paycheck. + +No bonus this time. The Income account started at a 0 balance. That means I can get a look at the baseline and minimums without too many other things pulling on my attention. + +To reach the 1,271 [.USD](United States Dollars) minimum to the Invest account, I'll need to drop the distribution to the Profit account by 1 percent and increase the distribution to the Invest account by 1 percent. + +I realized I did the quarterly transfer from the Profit account a month early. Those should be done on the first of January, April, July, and October. I got a bit ahead of myself. No big deal and can’t go back, just something to remember moving forward. + +I still have a portion of my check going to the Equipment Pass-through account. This should be the last paycheck where I need 250 USD to the Desktop account. I was planning on a 2021 iMac around July. I’m thinking I’ll wait to see if the rumored M1X Mac mini turns out to be true. Even if it’s not, I’ll probably go with a Mac mini instead of the iMac. I have too much gear that’s suited to a Mac mini setup and can continue to wait for improved designs and hardware despite the 2021 iMac being the first I thought was worth the drawbacks of an all-in-one computer. + +Moving forward, I'll reduce the amount going to the Desktop account to around 17 USD per paycheck. The money already saved will go to a desktop purchase in 2021, any remaining will go straight to the Invest account. I plan to keep the desktop for at least 5 years. By saving 17 USD each paycheck I should have enough saved in that time to replace it. If the 2021 desktop is still satisfactory by 2026, I'll stop sending money to the Desktop account and add it to the Income account; same with the phone (iPhone) and portable (MacBook Pro). + +Regardless, the 233 USD not going to the desktop account will go to the income account, which means I might be able to increase the amount going to the employer sponsored pre-tax retirement account — possibly reaching the 722 USD per paycheck maximum. + +## Being paid biweekly + +This paycheck is odd because It came in so close to the first of the month. The next one will also come in really close to the fifteenth. + +I don’t want the funds waiting around in the Income account for 11 days though. + +I’ll put 51.75 to the Invest account, 1 percent to the Profit account, 2 percent to the Tax account, 23 percent to the Runway account, and 22.25 percent to the Operating Expense account. + +I’ll pay off the credit cards and other bills and do it again around the actual fifteenth of the month. This should make my paycheck on the eighteenth closer to the consistent amount moving forward. + +## Web development + +I’m doing some web development work as a 1099. This will bring in some extra income and will need to be split a bit differently to account for taxes. + +I’m thinking 1 percent still goes to the Profit account, 15 percent to the Tax account (self-employment tax rate), 100 USD to the Invest account to be moved to the M1 Finance portfolio, the rest has been put into the Operating Expenses account. + +Note: These percents are based on the total amount paid, not the total amount received. There were merchant fees for processing payment that were taken out. + +## M1 Finance portfolio + +I use an [M1 Finance](https://www.m1finance.com) pie for insurance deductibles. It needs to carry a few thousand altogether at any given time. Right now I’m partially playing around with it. It has two categories of investments. Of course, it’s an experiment and probably is worth explaining. + +It’s a pie of pies. + +One slice is a pie I call Future Earth. It’ll carry global equities and bonds, including the United States. Right now it mainly carries the [.ETF](Equities Traded Fund) equivalent of VEXAX. + +The other slices are variations on the same theme. (This is where the nesting gets hard to explain.) I choose an individual company’s stock. The companies are ones I purchase from regularly or have purchased regularly in the past and would continue to do so if the need arose. I create a pie with a single slice made of that single equity. Then I create a pie made of one or more of those equity pies; if the company didn’t pay dividends since the last time I created one of the “Brand” pies, it doesn’t get added. My goal is that each Brand pie yields an estimated 4 percent or more in dividends. I create one of these Brand pies each quarter. + +I’ve been considering how to rotate them since I started the investment operations and I think I figured it out. This is my first go and will take a few years to test in practice. Let’s start with percentages. + +I’m building up to have 10 Brand slices at 1 percent each. The Future Earth slice will build up to being 11 percent. The Brand slice for the current quarter will take the remaining percent (capping out at 79 percent). At the end of the current quarter, the oldest Brand slice will be sold; if it has gained in value. Once sold, it will be removed from the pie. The current quarter (the one ending) will have its percentage reduced to 1 percent. A new Brand slice will take its place. 15 percent of the equity from the sale will be moved to the Tax account while the rest will be used to buy back into the portfolio. + +The theory might sound strange. Each Brand slice is built to be a high percentage of the overall pie. When the Brand slice has its target reduced to 1 percent, there is a gap between the target and current percent. Through regular contributions, by the time the value reaches the target, it should be time to sell that pie. Those funds are then redistributed amongst the other slices; helping the current Brand slice to increase in its overall percentage while the others "cool down." + +M1 Finance has said they try and perform sales in a tax-advantaged way, emphasizing long-term capital gains as opposed to short-term. The aforementioned should help guarantee that is the case. + +This rotation method should replicate the self-cleansing nature of index funds. If a company starts paying dividends or increases the dividend they pay out, their percentage in the pie could go up. If they lower the dividend payout or go out of business their percentage of the pie could go down. As new companies rise and I start using them, they could end up in the pie. The Future Earth pie helps shore up the volatility of the individual equities I choose. + +It's a buy-and-hold-forever strategy unless I stop purchasing products and services from the companies, they stop paying dividends, they leave the exchange, or some combination. + +## Mac Mini + +I’m thinking of going back to the Mac Mini setup I had around 2011 instead of going with the iMac. + +I’d still get rid of the Apple TV and my 32” television set, so, consolidation still happening. + +Here’s the rationale components. + +First, I purchased a keyboard in 2020. The [ZSA Moonlander](https://www.zsa.io/moonlander/). I appreciate the keyboard a great deal even though I’m still getting used to it. A keyboard comes with the iMac and the only added benefit I’d probably use is the Touch ID. + +Second, I purchased a space gray [Magic Trackpad](https://store.apple.com/xc/product/MRMF2LL/A) in 2020. It works well and is positioned between the two keyboard halves. One of the peripheral options for the iMac is a trackpad. I don’t want the Magic Mouse and input devices are required peripherals with the purchase of the iMac. At which point I’d have a new Magic Trackpad that wouldn’t be used. + +Third, I purchased a 24 inch monitor in 2020. It uses an HDMI connection. The iMac doesn't have an HDMI connection and I'm not sure I want to have a dual monitor setup; after years of having only a laptop, I'm good and can work around limited real estate. With the monitor and the mini *without* the keyboard and extra trackpad, I don't think the volume of the desktop setup would be much greater than the iMac by itself. + +Fourth, from a workflow perspective, one of the reasons I want to have a standalone desktop again is to let it run all the time and check email in the background to apply the presorting rules I've established in Apple Mail. The iMac is not required for this functionality, technically I could probably do it with my email provider but it's more convenient to establish the rules in Apple Mail. Therefore, I only need a machine that can run Apple Mail in the background and check my mail all the time. + +Fifth, from a production perspective, I'm looking to no longer need to plug my laptop into the port hub I have when I want to do work at my desk. I like the laptop for portability and I also appreciate having a permanent setup that's easy to start up at my desktop. + +Miscellaneous: I like the idea of being able to have my laptop open, off to the side with its own setup. There's about a 1,000 USD difference in price, which I can put toward the Invest account; it's already saved up. As odd as it might sound, from a personal branding consideration, the pink could work, but a full red version would blend in better. There's a rumor that there will be a redesign of the mini for 2021 and it will be released around WWDC 2021. + +In short, in 2020 I spent a lot of money updating the studio and sunk cost fallacy be damned, the new iMac should come with a few more enhancements and my current equipment should be a bit more worn before I chuck that aside and get the new iMac. + +At least that's where my brain is at right now. + +One of the things I like about having to save up for a purchase is it can help curb impulse purchases. Granted, it also requires discipline to not just throw down using a credit card; for me, using deposit certificates with a rollover date in the future helps here. + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210701/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210701/content.md new file mode 100644 index 00000000..f34ae004 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210701/content.md @@ -0,0 +1,33 @@ +--- +title: July 1st, 2021 paycheck +dateblock: + - 20210619 Created on + - 20211031 Updated on +data: + - [Debt, 0, 0, 0.28] + - [Cash, 2, 4, 9.94] + - [Invest, 0, 0, 0] + - [US Bonds - munis, 1, 2, 4.14] + - ["US Bonds - [.Gov't](Government)", 0, 0.5, 0.01] + - [US equities - small, 30, 35, 18.01] + - [US equities - mid, 30, 35, 22.69] + - [US equities - large, 30, 35, 45.22] + - [Int. bonds, 0, 0, 0] + - [Int. equities, 0, 2, 1.42] +--- + +# July 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +I increased the contribution to my 401(k) by 2 percent; 17 percent for this paycheck. + +The 401(k) change increases how much money goes toward small- and mid-cap [.US](United States) stocks to continue balancing the portfolio in keeping with the [investment policy](/finances/investment-policy). I've also reached the savings goal for the desktop computer purchase and have reduced how much I'll be sending that way every paycheck, which means I should be able to achieve that 1,271 [.USD](United States Dollars) to investment accounts every paycheck. With that said, if I put all of it into the total stock market index fund, I will most likely be throwing the balance of the portfolio off even more. + +The regular 150 USD is going toward the HSA (have not transferred out of HealthSavings yet as I’m hoping not to lose money in the transaction). I moved a little over 100 USD to the M1 Finance pie; purchasing a little of the Brand slice and the Future Earth slice directly (Future Earth is focused on small- and mid-cap US equities). The rest went to the total stock market. + +The hope is that by the end of 2021 I'll be able to achieve the desired portfolio balance without selling any positions. I should also have a better idea on percentages to go to the various places on a paycheck by paycheck basis; still dialing in. + +This is the paycheck in which I should have done the redistribution from the Profit account. Having done it in June, I won't do it this time. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210715/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210715/content.md new file mode 100644 index 00000000..bf44bb93 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210715/content.md @@ -0,0 +1,38 @@ +--- +title: July 15th, 2021 paycheck +dateblock: + - 20210704 Created on +data: +- [Debt, 0, 0, 0.16] +- [Cash, 2, 4, 9.94] +- [Invest, 0, 0, 0] +- [US Bonds - munis, 1, 2, 4.06] +- ["US Bonds - [.Gov't](Government)", 0, 0.5, 0.01] +- [US equities - small, 30, 35, 17.92] +- [US equities - mid, 30, 35, 22.62] +- [US equities - large, 30, 35, 45.15] +- [Int. bonds, 0, 0, 0] +- [Int. equities, 0, 2, 1.41] +--- + +# July 15th, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +I decided to leave the contribution to my 401(k) the same; 17 percent. Because I don’t have the same amount going to future equipment purchases I could probably increase it; however, I’m not feeling as stable in my employment at the moment and want to ensure cash on hand in case of layoffs, transition periods, or relocation. + +This paycheck is awkward because it was available on the first (wasn't supposed to arrive until the second) and is supposed to be the check used for the fifteenth. I'm still going to stick with the plan to make the moves to various savings accounts and will hold back what I can to pay bills and things until it's a bit closer to the fifteenth. + +1 percent to the Profit account. 2 percent to the Tax account. 47.72 percent to the Invest account. 23.5 percent to the Operating Expense and Runway accounts. The rest to the equipment release accounts. + +Things have been a bit crazy at work and I've been ordering in food a bit more at the moment; also found out that the app I use splits purchase from tips, which I appreciate but, only the purchase earns the three percent reward not the tips. My housing percentage is roughly 30. The membership fees are also a bit high this year because of the lifetime membership; so, it should be dropping through the rest of the year. Food and dining are roughly equal at the moment, which just reenforces how much cheaper it is to buy food and cook at home. + +I'm also seriously debating on dropping the indemnity insurance. I've been compiling a list of procedures I'm wanting to have performed before the end of the year. I will be calling the plan provider to ensure qualifying procedures and how much reimbursement and offsetting will be available. + +I'm admittedly concerned I'm not putting enough in the Operating Expenses and Runway accounts; this is another reason I'm considering dropping the indemnity insurance, which would recover that expense to establish a more steady baseline. The indicator here is that after projecting paying off my credits cards there isn't enough to cover half my housing costs from the Operating Expense account. I also don't have enough history with the Runway account to determine if its steady balance is increasing or steadily decreasing. (The [July 1st paycheck](/finances/building-wealth-paycheck-to-paycheck/20210701) was the first one where I transferred funds from the Runway account to the Operating Expense account to cover things.) + +The desktop fund has matured and is available. Given I've decided if I get a desktop computer it will be a newer Mac mini instead of the 2021 iMac, I can use about half the money saved in the desktop fund to help me dial in. + +I'm putting more into the Future Earth pie with M1 Finance than the Brand pie right now to keep moving the overall portfolio into balance. The Future Earth pie has a higher percentage in small- and mid-cap [.US](United States) equities than the Brand pie. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210801/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210801/content.md new file mode 100644 index 00000000..0b07fd42 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210801/content.md @@ -0,0 +1,62 @@ +--- +title: August 1st, 2021 paycheck +dateblock: + - 20210709 Created on + - 20210803 Updated on +data: +- [Debt, 0, 0, 0] +- [Cash, 2, 4, 9.94] +- [Invest, 0, 0, 0] +- [US Bonds - munis, 1, 2, 3.80] +- ["US Bonds - [.Gov't](Government)", 0, 0.5, 0.01] +- [US equities - small, 30, 35, 18.14] +- [US equities - mid, 30, 35, 22.25] +- [US equities - large, 30, 35, 45.22] +- [Int. bonds, 0, 0, 0] +- [Int. equities, 0, 2, 1.41] +--- + +# August 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +This is another awkward paycheck because it will actually be two. One on the sixteenth and another on the thirtieth. + +## Paycheck from the 16th + +I increased the 401(k) contribution to 20 percent. Still hoping to hit the maximum contribution limit for the year. A secondary benefit is that it should concentrate my contributions to small- and mid-cap equities. Because I’ve practically hit my contribution limit to my Roth IRA for 2021, which means there’s 250 USD every paycheck that can go somewhere else. The total US equities fund is weighted toward large-cap; so, redirecting the 250 USD there won’t help reach the desired portfolio balance as quickly as increasing 401(k) contributions. + +I cancelled the indemnity insurance I was trying out, which reduces per paycheck expenses by about 150 [.USD](United States Dollar). + +- 1 percent to Profit, +- 2 percent to Tax, +- 47.72 percent to Invest, +- 23.5 percent to both the Expense and Runway accounts, and +- the rest to the equipment replacement fund. + +I found 20 USD on the ground and got a check from a doctor visit I had already paid for. Not sure why but my health insurance covered it. + +## Paycheck from the 30th + +I increased the 401(k) contribution to 25 percent. Still hoping to hit the maximum contribution limit for the year. The deposit didn’t land before the first, so, the numbers don’t reflect that and I’m actually not sure it increased because the amount of my deposit was the same as last time. Could be a maximum percent or I didn’t get the adjustment done in time. + +- 1 percent to Profit, +- 2 percent to Tax, +- 47.72 percent to Invest, +- 23.5 percent to both the Expense and Runway accounts, and +- the rest to the equipment replacement fund. + +Not feeling stable at my employer right now. They’re looking for another contract for me, which I appreciate. I’m also looking outside as well. Because of this instability I’m holding a bit more cash than I think is necessary right now. + +The extended total market fund price dropped dramatically. I had cash inside my M1 Finance pie and bought in at the lower price. It was the lowest price I was able to purchase it for. I don’t intend to time the market ever but I have to admit it was kind of fun. I put cash back into M1 Finance and have it waiting there for my next paycheck. + +I have the Mac mini now, which means the television will get used a bit more, luckily the price of what I consider nicer TVs has dropped dramatically, so, if this one doesn’t last longer than a decade it shouldn’t be that big of a hit to replace. I’m also appreciating the potential it brings. + +My mailbox rules finished syncing with iCloud and I am appreciating a pre-sorted iOS inbox. I also appreciate not having to plug in my laptop and take up desktop space with the laptop. The improved cable management is also a nice addition. I am curious how this could affect things like laptop longevity. + + + + + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210815/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210815/content.md new file mode 100644 index 00000000..11df1aaa --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210815/content.md @@ -0,0 +1,37 @@ +--- +title: August 15th, 2021 paycheck +dateblock: + - 20210905 Created on +data: +- [Debt, 0, 0, 0] +- [Cash, 2, 4, 9.94] +- [Invest, 0, 0, 0] +- [US Bonds - munis, 1, 2, 3.65] +- ["US Bonds - [.Gov't](Government)", 0, 0.5, 0.01] +- [US equities - small, 30, 35, 18.22] +- [US equities - mid, 30, 35, 22.47] +- [US equities - large, 30, 35, 45.83] +- [Int. bonds, 0, 0, 0] +- [Int. equities, 0, 2, 1.42] +--- + +# August 15th, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Now I’m not so sure I’ve got the maximum per paycheck contribution to my 401(k). My paycheck this time was less than last time, which was unexpected. + +I’m also thinking I should cut back on some of the investing for a moment because I received a letter from the IRS stating I owed taxes and penalties due to under reporting my income. The short version is I was the beneficiary on an annuity and the owner passed away. I received around 20,000 USD, which I used to pay off some of my student loans. I did not report this and neglected to include the letter stating the funds weren’t taxable. + +So, until that’s cleared up, I want to reserve more cash in case I do owe some sort of fees related to that. Once that’s cleared up and if I still have some money remaining, I’ll probably do a lump sum purchase. + +## Update + +Nope. Haven't hit the maximum for my per paycheck contributions to the 401(k). With that said, I've done rough calculations and, if I leave it where it is until the end of the year, I will contribute the maximum per year. + +Further, the stock market seems to be taking a beating again in the middle of the month, which seems pretty par for the course. Of course, by taking a beating, it's still up from where I started, which is the real goal. I'm hoping to lose as little as possible in the beginning and let the two primary funds compound as much as possible from dividends and contributions. + +Overall the portfolio seems to be rebalancing pretty well. I'm considering adding another line to the table for alternatives. It would primarily be for when I reach drawdown and would be starting at 0 percent given I'm still in hardcore accumulation status until at least the first Coast FI number is reached. + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210901/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210901/content.md new file mode 100644 index 00000000..59578610 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210901/content.md @@ -0,0 +1,148 @@ +--- +title: September 1st, 2021 paycheck +dateblock: + - 20210816 Created on + - 20210901 Updated on +data: + - [Debt, 0, 0, 0.2] + - [Cash, 5, 10, 14.7] + - [Low correlation, 0, 1, 0.4] + - [Negative correlation, 0, 1, 0.4] + - [US equities - small, 24, 35, 18.9] + - [US equities - mid, 24, 35, 22.8] + - [US equities - large, 24, 35, 42.1] +--- + +# September 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Changes: + +1. Simplified the table and communication of where money lives; based on three allocation blocks. +2. Sold all individual stocks. +3. Created an M1 Finance pie representing allocations for each Coast FI goal. +4. Upgraded interactivity and information available in the spreadsheet. +5. Still waiting for the [.IRS](Internal Revenue Service) to tell me if I actually owe taxes and fees, so, pausing investments. + +## Simplified the table + +Was recently introduced to [Risk Parity Radio](https://www.riskparityradio.com) via [Choose FI](https://www.choosefi.com) and I’m appreciating the ideas there. Specifically this idea of what it is to be diversified in a portfolio. In short: + +> The "Holy Grail of Investing" is "making a handful of good uncorrelated bets..." It is "the surest way of having a lot of upside without being exposed to unacceptable downside." ~ Ray Dalio, *Principles* + +It’s not just uncorrelated with the stock market, it’s uncorrelated to each other. In fact, in some cases, we want to invest in something that historically goes up in value while something else goes down. + +I plan to have a stock-driven portfolio. Therefore it makes sense that the next largest allocation in my portfolio should have a negative correlation to stocks; this is where bonds come in, specifically, long-term [.US](United States) treasury bonds. The smallest part of the portfolio is relatively uncorrelated to cash, stocks, and treasuries. + +The equities will typically bring the most growth through capital appreciation and a bit in dividend income. The drawback to equities is they are volatile, which just means they can increase and decrease in value a great deal from day-to-day. + +When we talk about risk tolerance, this is what we’re talking about. How likely are you to panic if you see your portfolio or net worth drop 20 to 50 percent in a given day? + +In some cases, the overall stock market can be de down 10 to 20 percent compared to its previous all time high and stay there for years. For example, in 2000 the stock market dropped roughly 30 percent and didn’t go back to that previous level until 2007. Then, in 2007, the stock market dropped almost 50 percent and didn’t make it back until 2010. Taken as a single event, it’s basically a decade; a lost decade, if you will. + +This is sometimes referred to as risk capacity; how long can you wait for a recovery? + +My annual income is greater than my overall portfolio value. My total lifestyle cost is far less than my income. It would take me roughly two years to save up the same amount and I don’t plan on cutting back my hours or income within the next 10 years. + +I have a high risk tolerance and a high risk capacity. My portfolio is 100 percent stocks. + +When I start hitting those Coast FI numbers in my [investment policy](/finances/investment-policy), my risk capacity and portfolio allocation will begin to change. + +I feel like I’m starting late and playing a bit of catch-up. However, I can’t let that feeling cause me to make un-calculated and unnecessary risks. + +## Sold all individual stocks + +To save for my insurance deductibles I went with individual stocks; mainly to experience owning individual stocks. I’ve decided to step away from that for now. It was working, it was just cumbersome. + +I think I’m also starting to understand the difference between saving, investing, and owning for me. + +Saving means I have cash; a share in an economy. Investing means I spent cash to buy something else I hope will have greater future value. Owning is a specific type of investing because it comes with other rights and responsibilities. + +When I put a dollar in my credit union, I have a share in that credit union. My credit union uses those funds to give loans, part of which they pay me in the form of dividends. I have the ability to vote on who sits on the board and certain other decisions. I’m an *owner* of that credit union. + +My current balances and the dividends the credit union can pay aren’t that high right now and don’t outpace the annual reduced purchasing power of the money saved. I need to invest in something else if I want to be able to pay for my lifestyle in the future and not have to “hustle” until I’m dead. + +This is where indexed mutual funds come in. The indexed part of that is the key. + +A mutual fund is when a bunch of people pool their resources to invest in something (usually the stock market). There are two flavors of mutual fund: active management and passive management. Active management means someone is deciding what to invest in, when to invest in it, and how often; a portfolio manager. Passive management means there’s an explicit set of rules and changes to any given course of investment are based on those rules; usually these rules come in the form of an index. With this type of investing, I’m *not* the owner of the individual companies or asset; I own shares of the fund not the stock, bond, or alternatives market. + +Right now most of my investments are spread across the entire US stock market. Further, most of that is in index funds. I pay very low fees because I’m not paying a babysitter. I also don’t get to vote on things like budgets and board members for the 3,500 companies traded on the US stock exchange. + +I had a small bit of investments set aside in individual companies. I was an owner of those companies. I got to vote on budgets and board members. + +Given I’m just starting this journey into investing I want to experience as much as I can while the stakes are relatively low. A 10 percent mistake on 100 USD is 10 USD. A 10 percent mistake on a million is one hundred thousand dollars. + +Investing in individual companies wasn’t a financial mistake and I’m grateful for the experience, I may even do it again some time. It just added a layer of complexity I didn’t want to deal with. + +If I’m understanding the tax implications around how this works: + +1. I should not be taxed on the money spent to buy shares. That money was already taxed and the government can’t tax the same dollar twice. +2. I will be taxed under the capital gains rules for the remainder of the sale. These will be short-term capital gains, which means I’ll be taxed my ordinary tax rate on around 60 USD; not the over 1,000 USD invested. + +Like I said, it was going great and the ride was pretty stable. I also really liked being the owner of the companies I had. Right now I’m just looking to simplify and consolidate things. + +## Coast FI pies + +One of the first things we created was the Coast FI stack in the [investment policy](/finances/investment-policy). The Coast FI stack creates 5 milestones; one for each Coast FI number. I created what I believe will be the portfolio I’ll want by the time I reach FI. It is still stock-driven and uses the same two funds as the base. From an historical perspective it seems to have the performance characteristics I’m looking for: + +- Sharpe and Sortino ratios greater than 1. +- Depending on which dataset I use the worst drawdown is 6–13 percent and that lasts roughly 6 years. + +Regarding the drawdown range, the dataset that says the drawdown could be 13 percent is basing it off 3 years of data. The dataset that says 6 percent goes back to the 1970s. Luckily, we have about 10 years before I think I’ll be “all in” on this allocation, so, more data will come in and we can adjust as needed. + +With that said, it doesn’t mean we can’t experiment with smaller balances. It also means we can create allocations for each milestone. This is similar to the way a target date index fund would work. + +I built pies in M1 Finance, one for each milestone. I put each into a containing pie and will throw 180 USD to be divided equally to the 6 sub-pies; 5 Coast FI numbers plus the 100 percent stock allocation I’m in now. This way I can watch them perform over time and see if I’m comfortable with the swings up and down. + +I’ve created two pies for intermediate savings (purchased 5-plus years away); one pie for electronics (computers and cables, not cellphones) and insurance deductibles (what I was using my individual stock portfolio for). These both use the proposed Mark 1 pie, which is what I’m hoping to use as the drawdown portfolio. If I can still buy electronics from the electronics fund and pay insurance deductibles from the insurance fund, we’re probably okay. This will also introduce me to the tax implications of these various holdings. + +I created another pie to start saving for a car; thinking in 10 years for the van life thing. I’m estimating 30,000 USD for the car. There will be roughly 240 paychecks between now and then. I want to simulate the Coast FI stack at this smaller scale. I’ll create 6 milestones for the car savings and convert as each milestone is achieved. + +The FI Experiments pie with the milestone pies underneath is the control, this pie has no further contributions added to it. We also have two pies testing continued contributions for the Mark 1 allocation; the transition milestones use dot notation with a prefix of 0 (0.0, for example, is where we are now). We also have a pie that should act as something of an accelerated microcosm. + +I’ll consider rebalancing once a year using bands. So, on the day, I’ll check each sub-portfolio and, if the spread is wide enough to warrant it, I will buy and sell accordingly. We shouldn’t need to rebalance most of the sub-portfolios because they’ll be rebalanced through contributions; only the control group should require rebalancing, which we’ll only do if they meet the criteria on the day and the bands will be pretty wide. Buy and hold. Buy. And. Hold. + +It’s important to note (or possibly reiterate), at the timescale of 10 years and until we hit 100 thousand dollars, we’re not getting much assistance from compounding. Most of the fair market value of the portfolio (and alt portfolios) will be from principal and appreciation of the assets purchased. + +These posts use my overall net worth statement, which combines all of the sub-portfolios and accounts. Further, because I’ve essentially used my money to purchase shares of index and mutual funds, most of my money is “tied up” (just like someone who owns a home). Finally, one could argue that my assets are more liquid than a home; however, it still requires someone on the other side willing to pay what I’m charging for the shares. + +There are two hopes: + +1. Between appreciation and dividends, when I sell shares in the funds, the money I receive will have at least the same purchasing power as the principal I put in. +2. Alternatively, between appreciation and dividends, if I sell all my shares, I will get the same amount I put in and maybe a bit extra. + +A note on M1 Finance is that setting up the pies and selling the stocks revealed something interesting. I was surprised that M1 Finance didn’t sell my lots in a certain fund in order to buy the same fund. It appears the calculations used look more at the lots I hold and not the pies themselves. I don’t know for sure if it works this way, but it appears because more money would end up in the same extended market fund than was already there, M1 Finance only bought more. + +## Upgraded the spreadsheet + +I still use a spreadsheet for high-level planning (and making these entries. I use [Apple Numbers](https://www.apple.com/numbers/), which allows you to add multiple tables to a single sheet in a way I haven’t seen with other spreadsheet apps. + +For the tables I use to build these entries I have one where I put the balances and fair market values for all the things. The data in this table is used to update numbers in the portfolio table (more on that later). The data in the portfolio table is used to update some of the percentages used above. Then I export another table to a file I upload to [portfolio visualizer](https://www.portfoliovisualizer.com/backtest-portfolio), which tells me the percentages for small-, mid-, and large-cap equity investments. The percentages table also has the bands used to determine if rebalancing will occur. I added conditional highlighting to the cells so, if they need more money the cell is green, red if that component needs less money, and plain text if it’s within range. + +The portfolio balances table does the same thing while separating the portfolio into four sub-portfolios: + +- taxable, +- tax-deferred, +- tax-free, and +- triple tax advantaged. + +Each sub-portfolio shows where I’m out of balance. + +Another table helps me determine paycheck distributions, saving for known future expenses, and progress toward the Coast FI stack milestones. + +## Waiting for the IRS + +I contacted the IRS to verify if my correspondence was received. They weren’t able to confirm but updated my account noting the call. They told me it could take a few weeks for acknowledgment by mail. And a few more weeks to make a determination. + +I will continue to hold back investing in anything except my 401k so I have plenty of cash on hand, just in case. Im still trying to reach the maximum 401k contribution for the year. + +When my last paycheck for the year comes in, I’ll reduce my per paycheck percent to something that allows me to hit the maximum each year while still taking advantage of the full employer match. + +This year has been interesting as I keep shifting allocation figures around. Next year I’m hoping for more consistent redistribution amounts across the board. I am appreciating that the overall portfolio is coming into balance. + + + + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20210915/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20210915/content.md new file mode 100644 index 00000000..741a94e9 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20210915/content.md @@ -0,0 +1,34 @@ +--- +title: September 15th, 2021 paycheck +dateblock: + - 20210904 Created on + - 20210917 Updated on +data: +- [Debt, 0, 0, 0.5] +- [Cash, 5, 10, 15.7] +- [Low correlation, 0, 1, 0.3] +- [Negative correlation, 0, 1, 0.4] +- [US equities - small, 24, 35, 20.0] +- [US equities - mid, 24, 35, 23.0] +- [US equities - large, 24, 35, 39.9] +--- + +# September 15th, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +For the 401k I took the 19,500 [.USD](United States Dollars) I can make in a year and subtracted my contributions so far. I divided that difference by how many paychecks I think I’ll get before the end of the year. That should give me a rough, per paycheck, amount. The 25 percent I had last time was too high, so, I reduced it to 20 percent this time (still too high but closer). (Oddly enough my net pay was the same despite my contribution being almost 200 USD less.) + +I should get to experience what it’s like to have short-term capital gains to report because of the selling of the individual stocks I made the other day. + +I’m also planning to sell the municipal bond fund and end up with short-term capital losses. Im hoping here because this is a mutual fund and I’m not sure how that side of it works. My understanding with funds is that I could wind up with realized gains and losses even if I don’t sell anything. We shall find out. + +My hope overall is that the capital gains from selling the stocks will be somewhat offset by the capital losses from selling the municipal bonds. Either way, the gains are less than 40,400 USD and would fall in the zero tax bracket there. Making the “big” adjustments early is cheaper when the balance is lower. + +I did receive a letter from the [.IRS](Internal Revenue Service) confirming they received the packet of documentation I sent them. Still waiting for resolution. Hoping it’s that I won’t owe more taxes and fees. Also hoping I can get a lot of this cash invested sooner rather than later. + +My new gig is feeling pretty stable and they seem to like me. So, I’ll probably run a bit leaner on the cash side once the IRS thing is cleared up. + +Other than that, not a lot to report. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20211001/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20211001/content.md new file mode 100644 index 00000000..eefb4df1 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20211001/content.md @@ -0,0 +1,50 @@ +--- +title: October 1st, 2021 paycheck +dateblock: + - 20210918 Created on + - 20211001 Updated on +data: +- [Debt, 0, 0, 0.4] +- [Cash, 5, 10, 16.5] +- [Low correlation, 0, 1, 0.4] +- [Negative correlation, 0, 1, 0.4] +- [US equities - small, 24, 35, 20.2] +- [US equities - mid, 24, 35, 22.9] +- [US equities - large, 24, 35, 39.1] +--- + +# October 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Reduced the 401k contribution to 18 percent. I’m hoping to basically land on the desired 15 percent by the end of 2021. Gives me something to do while passing the time for the [.IRS](Internal Revenue Service) resolution. + +Won’t be doing the quarterly draw from the savings account this time as I wait for the IRS thing to clear up. When it does, I’m hoping to do the draw and push more money into the overall portfolio. + +## Revenue + +Feeling confident my job and paycheck will be here tomorrow. Having said that, sitting on all this cash is a bit frustrating. + +The market dropped a fair amount a bit ago and I really wanted to jump in and purchase more shares in all the things. + +## M1 Finance experiments + +The performance of the six experimental portfolios has been interesting to watch. + +I created six pies in M1 Finance. On one extreme there is a balanced total stock market; 66 percent extended market index fund and 34 percent total stock market. This gives me an even distribution between small-, mid-, and large-cap stocks. On the other is a potential risk parity style portfolio I’m considering. + +In the beginning they acted as hoped. If the balanced stock market pie was down, the risk parity style wasn’t down as far; by almost half, indicating lower volatility on the drawdown side. However, with this paycheck (as of the 24th), they’re all down roughly the same. + +I don’t contribute more money to these experiments, I’m just letting them go. + +I’m wondering if this is the way this will be moving forward. They’re all down about 1.25 percent. When the market dipped below 1.75 percent, the risk parity style portfolio was only down 0.75 percent. + +Anyway, given I’m just starting these experiments, there’s not enough data to see a trend. And, of course, past performance doesn’t guarantee future performance. + +## Update + +Doing the math again, 18 percent was too low for the 401k. I decided to capture the amount for the given percent and will bump it back up to 20 percent for the next paycheck. + +Sold my tax-exempt bonds and used it as the base to buy into the extended market fund. That puts me almost to my preferred two fund approach. Still waiting to see if my request to change the funds available in my 401k will go through. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20211015/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20211015/content.md new file mode 100644 index 00000000..0bcc02b5 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20211015/content.md @@ -0,0 +1,28 @@ +--- +title: October 15th, 2021 paycheck +dateblock: + - 20210918 Created on + - 20211017 Updated on +data: + - [Debt, 0, 0, 0.4] + - [Cash, 5, 10, 13.8] + - [Low correlation, 0, 1, 0.3] + - [Negative correlation, 0, 1, 0.4] + - [US equities - small, 24, 35, 21.9] + - [US equities - mid, 24, 35, 23.9] + - [US equities - large, 24, 35, 39.1] +--- + +# October 15th, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Upped the 401k amount to 20 percent; it seems like the change might take a paycheck before taking affect. Still waiting for the [.IRS](Internal Revenue Service); so, avoiding spending and not investing, which is a bummer considering the market was down significantly pretty much all of September. + +I’m still cash heavy but spending cash is pretty easy; though I’ll most likely be investing most of that...once the IRS situation is cleared up. + +We’re planning on moving in the beginning of 2022. The target area should be a little less expensive than where we are now. + +And equities have started to bounce back. diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/20211101/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/20211101/content.md new file mode 100644 index 00000000..950aa8d4 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/20211101/content.md @@ -0,0 +1,35 @@ +--- +title: November 1st, 2021 paycheck +dateblock: + - 20210918 Created on + - 20211031 Updated on +data: +- [Debt, 0, 0, 0.5] +- [Cash, 5, 10, 14.1] +- [Low correlation, 0, 1, 0.3] +- [Negative correlation, 0, 1, 0.3] +- [US equities - small, 24, 35, 21.8] +- [US equities - mid, 24, 35, 23.6] +- [US equities - large, 24, 35, 39.3] +--- + +# November 1st, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Upped the 401k amount to 20 percent; it seems like the change might take a paycheck before taking affect. Still waiting for the [.IRS](Internal Revenue Service); so, avoiding spending and not investing, which is a bummer considering the market was down significantly pretty much all of September and part of October. + +I’m still cash heavy but spending cash is pretty easy; though I’ll most likely be investing most of that...once the IRS situation is cleared up. + +We’re planning on moving in the beginning of 2022. The target area should be a little less expensive than where we are now. + +And equities have started to bounce back. + +## Health savings account + +I sold my position at my other [.HSA](health savings account) provider. Now that is in cash as well. + +Waiting for the transfer to the HSA provider I will be sticking with for the foreseeable future. + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/_20211115/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/_20211115/content.md new file mode 100644 index 00000000..77b6519b --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/_20211115/content.md @@ -0,0 +1,35 @@ +--- +title: November 15th, 2021 paycheck +dateblock: + - 20211101 Created on + - 20211031 Updated on +data: +- [Debt, 0, 0, 0.5] +- [Cash, 5, 10, 14.1] +- [Low correlation, 0, 1, 0.3] +- [Negative correlation, 0, 1, 0.3] +- [US equities - small, 24, 35, 21.8] +- [US equities - mid, 24, 35, 23.6] +- [US equities - large, 24, 35, 39.3] +--- + +# November 15th, 2021 paycheck + +{!!dateblock!!} + +{!!data!!} + +Upped the 401k amount to 20 percent; it seems like the change might take a paycheck before taking affect. Still waiting for the [.IRS](Internal Revenue Service); so, avoiding spending and not investing, which is a bummer considering the market was down significantly pretty much all of September and part of October. + +I’m still cash heavy but spending cash is pretty easy; though I’ll most likely be investing most of that...once the IRS situation is cleared up. + +We’re planning on moving in the beginning of 2022. The target area should be a little less expensive than where we are now. + +And equities have started to bounce back. + +## Health savings account + +I sold my position at my other [.HSA](health savings account) provider. Now that is in cash as well. + +Waiting for the transfer to the HSA provider I will be sticking with for the foreseeable future. + diff --git a/content/public/finances/building-wealth-paycheck-to-paycheck/content.md b/content/public/finances/building-wealth-paycheck-to-paycheck/content.md new file mode 100644 index 00000000..f3f59188 --- /dev/null +++ b/content/public/finances/building-wealth-paycheck-to-paycheck/content.md @@ -0,0 +1,34 @@ +--- +title: Building Wealth Paycheck to Paycheck +dateblock: + - 20210216 Created on +loglist: true +--- + +# Building Wealth Paycheck to Paycheck + +{!!dateblock!!} + +I'm present- and future-oriented. The concept of building a restrictive budget and then potentially beating myself for past decisions is a total turn off for me. I needed something simple-ish. + +I'm an optimistic tinkerer with both a high risk tolerance and capacity who needs guardrails to keep me from tinkering my way into negative returns. I'm more flow-oriented than time-oriented, which makes tying things to events and need easier than tying them to time. + +I see finances in terms of two primary scales: + +1. Immediate-need and long-term future. +2. Negative-return and high-return. + +To appease the tinkerer and risk taker in me, I needed a method where I was hands-on; moving things around and logging into accounts — feeling like those scenes in movies where hackers are doing all this insane stuff on computers that is literally impossible and not how computers work. With that said, I wanted decision making to be as automated as possible. I didn't want to have to call or sign on to a bunch of places to cancel automatic payments should I find myself living in my car again or couch surfing because I couldn't afford to pay rent somewhere. + +These desires and what I knew of myself became my [budgeting method](/finances/budgeting). + +This series chronicles the moves I started making regarding finances — at least from April of 2021 as I didn't really think about it in the first couple of months of 2021. + +I'll probably avoid talking dollar amounts mainly because, for me, the systems and habits should be applicable regardless of dollar amounts. My income can be pretty volatile and I don't want a system that depends on me making a certain amount of money. I think it's easier to adjust percentages and think in those terms than it is to think in terms of dollar amounts — I'm not what I consider to be a numbers-oriented person. Besides, the basic system and habits worked when I was living in my car and still work now that I'm not. + +## Conclusion + +The related entries are my journey paycheck-to-paycheck. They are listed in reverse chronological order. + +{!!loglist!!} + diff --git a/content/public/finances/content.md b/content/public/finances/content.md new file mode 100644 index 00000000..e9067414 --- /dev/null +++ b/content/public/finances/content.md @@ -0,0 +1,66 @@ +--- +title: Finances +disclaimer: "I'm not a financial planner or advisor and don't play one on the Internet. This content is for informational and entertainment purposes only and does not constitute advice. Please consult with your financial advisors and financial institutions." +dateblock: + - 20210216 Created on + - 20210528 Updated on +--- + +# Finances + +{!!dateblock!!} + +Talking about money has always felt foreign to me. It's one of those things that's socialized to be taboo. If you're doing poorly, people may not like you. If you're doing well, people may not like you. So, just don't talk about it. At least that appears to be the American ethos when it comes to money. + +Historically, I haven't been good at earning money; however, I've always been decent enough at knowing what to do with it when I have it. I worked at a credit union for six years and was trained on multiple topics there. I worked in the call center and was introduced to all sorts of individual strategies. As a coach I often recommend reducing friction to decision making. I have found establishing values, principles, practices, and tools early helps tremendously in automating decision making. These values, principles, practices, and tools don't need to be limited to the topic area; there's room for overlap. + +The following sections begin with my personal values, principles, practices, and tools with additional elements that I may not incorporate into my personal set but still think are viable. If you think any are missing, please let me know. + +## Values + +1. Autonomy: When I do the [Motivators exercise](/design-your-life/motivators) on myself, the ability to "choose my own adventure" ranks second; every time. +2. Index funds over managed funds; index or mutual funds over individual stocks and bonds. +3. My values and principles over social norms and mores. +4. Intellectual legacy over financial legacy over genetic legacy. +5. Businesses over governments, state governments over federal governments. +6. Cooperative business structures over privately held over publicly traded. +7. The most constrained over the most funded. +8. United States over International: 70 to 30 percent plus or minus 30. +9. Corporations over governments. +9. Equities (ownership) over bonds (lending): 70 to 30 percent plus or minus 5. +10. Local governments over federal governments: 70 to 30 percent plus or minus 30. + +## Principles + +1. Corporate profits favor owners; owning equity shares makes you an owner of the company. +2. The borrower is slave to the lender; owning bonds makes you the lender to the government or corporation. +3. Dividends created in a vehicle don't need to compound in that vehicle. +4. If you want to go fast, go alone. If you want to go far, go together. +5. Rising tides lift all boats and don't get caught skinny dipping during receding tides. +6. Money is food, not blood. +7. Be in the market and don't try to beat the market. + +## Practices + +1. Maximize revenue, minimize spending. +2. Combined expense ratio less than 1%. +3. Dividend promotion. +4. Maximize the number of people directly and indirectly supporting your progress. +5. Always think in terms of meeting in the middle. +6. Pay yourself first. +7. Promote dividends earned in vehicles returning less than 7% per year to vehicles that historically return higher. +8. Decisions should consider everything else before trying to generate higher than average rates of return. (See principle 7.) +9. [Building Wealth Paycheck to Paycheck](/finances/building-wealth-paycheck-to-paycheck). + +A note on number 7, I have a savings account at a credit union that earns roughly 6% on the first $500 when you meet certain criteria and 0.15% for each dollar above the first $500. I would like all the dividends to be promoted to index funds. Further, I plan on using my bond funds as simple interest savings accounts where I deposit an initial amount and have the dividends not reinvest. More on this in my [investment policy](/finances/investment-policy). + +## Tools + +1. [Personal Capital](https://www.personalcapital.com) for tracking the majority of my portfolio; some vendors aren't accessible to them at the moment, but it gets me close enough. In particular I appreciate the "You Index" and "Asset Allocation" tools. +2. [Wave](https://www.waveapps.com) for personal and business account transaction aggregation and budgeting. +3. [Stripe](https://stripe.com) and [Square](https://squareup.com/us/en) for payment processing, appointments, and the like. +4. One insured spending account: All non-business outflows come from this account. +5. One or more federally insured, interest bearing savings accounts. +6. One or more regulated retirement accounts. +7. One or more regulated taxable accounts. +8. A well-maintained [investment policy](/finances/investment-policy). diff --git a/content/public/finances/investment-policy/content.md b/content/public/finances/investment-policy/content.md new file mode 100644 index 00000000..529194d2 --- /dev/null +++ b/content/public/finances/investment-policy/content.md @@ -0,0 +1,181 @@ +--- +title: Investment Policy +dateblock: + - 20210216 Created on + - 20210828 Updated on +coming-soon: +- review agile manifesto +--- + +# Investment Policy + +{!!dateblock!!} + +An investment policy is entered between a financial advisor and yourself. In my case, I am the financial advisor and think it is beneficial to enter an agreement with myself when I am at peace. I want to do it while I'm at peace and in a good place because when things go pear-shaped I can come back to the mechanical side of things. + +I started with the outline developed by [The White Coat Investor](https://www.whitecoatinvestor.com/how-to-write-an-investing-personal-statement/) and here is my investment policy. + +## Financial goals + +In keeping with the [meet in the middle](/finances) practice, I want to reverse engineer from the least acceptable to most acceptable retirement ages that are both most likely. + +This gives me what I refer to as a Coast FI stack. Five Coast FI numbers with decreasing retirement ages, up until the point that the age I should be able to achieve the number corresponds (roughly) with the retirement: + +- Coast FI 1: $155,034 + 1. Starting at age: 41 + 2. Achieved by age: 43 + 3. Retire by age: 67 + 4. Starting balance of: $47,000 +- Coast FI 2: $170,925 + 1. Starting at: 43 + 2. Achieved by: 44 + 3. Retire by: 65 + 4. Starting balance of: $155,034 +- Coast FI 3: $220,056 + 1. Starting at: 44 + 2. Achieved by: 46 + 3. Retire by: 60 + 4. Starting balance of: $170,925 +- Coast FI 4: $322,304 + 1. Starting at: 46 + 2. Achieved by: 48 + 3. Retire by: 55 + 4. Starting balance of: $229,056 +- Coast FI 5: $453,515 + 1. Starting at: 48 + 2. Achieved by: 50 + 3. Retire by: 50* + 4. Starting balance of: $322,304 +- FIRE: $500,000 + +### Details + +I created this using a Coast FI calculator. Coast FI is the amount of money you would need to maintain your current total lifestyle cost at a specified future age or date, without having to save another dollar; the invested amount will grow to within a "safe withdrawal rate" by the specified age. + +I used the [Coast FI calculator from Wallet Burst](https://walletburst.com/tools/coast-fire-calc/) (I can’t speak to the quality of content on the rest of the site, but I appreciated the calculator). + +I chose a growth rate of **8%**, which might be considered conservative for the market in general. I chose an inflation rate of **3%**, which is the average for the last 100 years and higher than any given year from 2010 to 2021. I used the rule of thumb for a safe withdrawal rate of **4%**, which hypothesizes that you can draw down your net worth by 4% each year and live off those investments alone for around 30 years, at least. + +My current total lifestyle cost is roughly $17,760 and I used **$20,000** to give myself a bit of a raise. I still don’t have a normal year’s worth of data to confirm this specific dollar amount. Also, this is total cost of lifestyle, not income. As of August 2021 my expenses were at 19,000 USD, however, 2021 is an outlier year given things I’ve purchased and paid for in full as it were. + +I set the monthly contributions to **$4,000**, which is somewhat optimistic and will be adjusted if month-after-month I'm not able to achieve that amount. With that said, these numbers do not include 401(k) contributions made along the way beyond the initial net worth at 41. + +This data can be used to generate a static [.FIRE](financial independence retire early) number: `total lifestyle cost * 25`. My FIRE number is $500,000. + +Because the numbers account for inflation, they will be in current dollars but may be higher actual balances in the future (1 USD being worth 0.60 cents later means 1 future dollar may be roughly 1.40 USD at that future time). + +This gave me the Coast FI number and the age at which I should hit that number. I changed the net worth to be that Coast FI number. I changed the starting age to be the age the calculator estimated I’d hit that number. And I reduced the acceptable retirement age. + +For the last row to be achieved, I need to make an extra $2,000 in contributions per month, which should be covered and a bit more by the 401(k) I'm not formally putting in calculations. + +There's my primary retirement goal; able to “retire” by 50. By breaking it down I’m setting micro-goals that aren’t a decade away. Let me see if I can get the $155K by age 43. If I do that, let me see if I can get the $170K. And so on. And, if I can make that first one, then I'm in a comfortable position to believe that I will be able to retire at the latest acceptable time for me, which should come with other benefits I'm not counting on (social security, medicare, and the like). + +Another thing I appreciate about the table is that it's not bullets and a lot of words. I can scan down the left column for how old I am, scan across and see how close I am to achieving that level of investment, which is not representing my total net worth (traditional savings accounts, real estate, and similar). + +## Investments + +The items in the following that are taken directly (or paraphrased) from White Coat Investor are indicated in italics. + +- *I will strive to minimize the effects of taxes and expenses on our investment returns.* +- *My primary investment vehicles will be broad-based index funds, preferably in tax-advantaged accounts.* +- *I will buy, hold, and not panic during market corrections; unless I lose all faith in American businesses, governments, and money.* +- My savings rate and returns will be determined on a per-paycheck basis as a natural result of the [Building Wealth Paycheck to Paycheck](/finances/building-wealth-paycheck-to-paycheck) workflow. +- I will do what I can to vote with my dollars on the individual level and spread the level evenly in my personal investments. +- I will contribute at least $100 per paycheck to long-term savings. +- I will use a modified [total stock market and chill](https://www.liveoffdividends.com/financial-independence-order-of-operations/) strategy. +- Retirement withdrawal rate will be 3.5 percent. If portfolio increases by 50 percent over the course of 3 years, increase annual withdrawal by 10 percent (not the withdrawal rate). ex. Year 1 withdrawal twenty thousand [.USD](United States dollars); year 2 withdrawal twenty thousand USD; year 3 withdrawal twenty thousand USD; year 4, if portfolio value is 50 percent more than in year 1, withdrawal twenty-two thousand. Presuming next 4 years do the same, withdrawal twenty-four thousand two hundred USD. (Modifications are possible depending on [age and health](https://www.choosefi.com/flexible-spending-rules-for-early-retirees/)). + +Notice each of these are aligned with my financial [values, principles, and practices](/finances). + +## Asset allocation + +Investments will be in three brokerage accounts: + +- Roth IRA: + - post-tax going in (taxes are paid), + - tax-exempt while growing, and + - tax-exempt on withdrawal. +- Tax-deferred (traditional IRA-like): + - potentially pre-tax going in (I make too much annually to qualify), + - tax-exempt while growing, and + - taxed on withdrawal. +- Taxable: + - post-tax going in, + - taxed on dividends gained, and + - taxed on capital gains upon withdrawal (realized). + +For the taxable account there are potential tax advantages for selling a position with capital losses. It's called tax loss harvesting. I don't plan on doing anything there unless or until I fully understand it. + +I have multiple buckets (I'll put debts in this description despite them not being assets in the strictest sense), each is given a range of percentages it can or should be during any given pay period. + +The cash accounts use the flow-based approach described in [May 1 Paycheck](/finances/building-wealth-paycheck-to-paycheck/20210501) and [Time- and Flow-Oriented Budgets](/finances/budgeting). I'm sure over time these percentages will need to shift and, as they do, this table will be updated. I'm also not going to be very strict on the actuals matching or being within the targets. + +### Coast FI 1 (43 years old) + +When I reach Coast FI 1 (or leaving my current employer), I will likely roll my 401(k) into my Traditional IRA account. I am considering leaving one 401(k) open somewhere to draw from around age 55, depending on the rules of the 401(k) as this would save me from having to wait until 60. + +### Coast FI 2 (44 years old) + +Contemplate opening bond or similar income-generating accounts inside the IRA accounts. These dividends are non-taxable and would be rolled directly into the equity fund(s) in those accounts. This shouldn't count as a contribution; therefore, I can max out the contributions to the IRAs, if possible while using the IRAs to maintain overall portfolio balance, which will probably see bond limits to 1 and 2 percent. + +### Coast FI 3 (46 years old) + +Increase bond limits to 2 and 4 percent. + +### Coast FI 4 (48 years old) + +Increase bond limits to 4 and 8 percent. + +### FIRE-able (50 years old) + +Increase bond limits to 20 and 30 percent. + +At this point, I should have enough in the taxable account to continue living my modest lifestyle until age 60, when I can start withdrawing from the IRAs. I hope to be in a position where I am earning income from non-investment sources; the plan shifts at this point, based on current conditions and over the next two years. + +1. I'll save up to one year's worth of expenses in cash. +2. If I'm planning on pulling the cord at the end of the two years (to either retire or take a drastic pay cut — Barista FIRE), I will adjust to a 70 percent stock holding. + +### Beyond FIRE + +I have no idea. + +The fact my Agile-brain has even allowed me to come up with a 10 year "plan" is crazy-talk; one of the Agile values is adapting to change over following a plan. + +In other words, this is a plan like any other, once met with reality it may become rather moot. + +What makes me feel comfortable about it is it's based on the value of the portfolio, which is the crux of the whole thing. So, if time slips a bit, I still have the same goal and, once I hit the first Coast FI number, the retirement date is acceptable, though not ideal. + +### Details + +Asset allocation is about how much of your portfolio is in different types of buckets. The rule of thumb here being to diversify. Before broad-based index funds and the like, being diversified meant choosing individual businesses in a variety of industries. You might have invested in a few technology companies, a few manufacturing, a few energy, and so on. When it comes to index funds, these rules don't have to apply, because you could have a single fund that touches the entire market (at least the entire market traded through the channel you're using - the US exchanges in my case). + +- In the beginning, while actively maintain traditional employment, the portfolio will be 95-99% equities and 1-5% bonds; preferring higher equities. +- I will strive to favor equal distribution across the entire American stock market (all US businesses traded on the exchange); favoring small-cap businesses when not equally weighted. +- Diversification beyond equities and bonds will not be done until achieving level 1 Coast FI, if ever. + +## Emergency fund (cash and credit) + +Most of my emergency fund will use revolving loans; I'm sure someone's head somewhere just exploded. The reason I think this is a decent choice is my revolving loans have relatively low interest rates; less than 11 percent. + +I also have a wide definition of "cash" when it comes to this emergency fund; in short, cash means I can get my hands on it within 5 days and it is earmarked for short-term needs (one year or less). + +Anywhere from 1 to 2 months in traditional savings and checking accounts. Another 2 to 3 months in a tax-exempt bond fund. Insurance deductibles in an equities portfolio designed to earn dividends, have a yield greater than 3 percent, and afford me the opportunity to vote on matters presented to owners; dividends will be moved elsewhere. + +Replacement costs for tools like cellphone, laptop, and the like will be saved over time. The tool will only be replaced if the current one no longer serves or the new one will bring as much joyful utility as the current one. + +## Housing and paying off debt + +- Monthly housing expenses will be no more than 25-30% of my net income. +- I will not seek to own my own home unless it is part of an assisted living strategy. +- Debt will be avoided when possible and paid off immediately, while maintaining at least a 10% contribution per paycheck to savings and investments. + +## Spend and giving + +- I will use the [Building Wealth Paycheck to Paycheck](/finances/building-wealth-paycheck-to-paycheck) method to help automate these decisions and habits. +- This will occur every time income is received. +- Contributions will be done in a way that helps maintain desired asset allocations. + +## Changes + +- This policy will be revisited regularly. +- Will be changed based on the financial goal being pursued first. diff --git a/content/public/health-and-wellness/content.md b/content/public/health-and-wellness/content.md new file mode 100644 index 00000000..289acfb3 --- /dev/null +++ b/content/public/health-and-wellness/content.md @@ -0,0 +1,82 @@ +--- +title: Health, wellness, and physicality +dateblock: + - 20210531 Created on +disclaimer: "I'm not a doctor much less your doctor nor am I a personal stylist. Just a guy on the Internet sharing experiences and what I'm doing and considering regarding my own physical and mental health. This content is for informational and entertainment purposes only and does not constitute advice. Please consult with your doctor and wellness team." +--- + +# Halth, wellness, and phsyicality + +{!!dateblock!!} + +Given my historical journal entries it seems every six years or so I get a bug in my ear to look at my physical health, physical appearance, style and whatnot. + +In 2009 it was urban obstacle course style with [Parkour](https://en.wikipedia.org/wiki/Parkour) and [Freerunning](https://en.wikipedia.org/wiki/Freerunning). I was more about Parkour than Freerunning. If you've touched any content in the [time-and-space](/time-and-space) area I'm sure you're shocked. + +In 2015 it was what I called "The Aesthetics Project." It was learning how to exercise indoors, buy clothes that fit and what my palette was. + +In 2021 it's about longevity; [getting out of debt](/finances) had a pretty profound affect on me. + +- Nutrition: Ingestion and internal organs + - Change and Rules + - Don't leave house w/o eating oats (or breakfast) + - Morning vitamin and dose w/ breakfast + - Eat high caloric end of day meal + - End of day fish oil and dose w/ dinner + - Endocrinologist and nutritionist + - Keep + - Intermittent fasting + - Remove + - Constipation + - Don't eat regularly - and it's haphazard in its qualities + - Don't take meds and vitamins consistently + - Sugar (refined) and lactose +- Physicality (see Nutrition, Movement, Plastic Surgery): Bones, muscles, and appearance + - Change + - Exercise training: Warm-up and cool-down from Nerd Fitness for three days in the week + - Skin: Switch to Dove soap, immediately. Weekly exfoliation. Cooler showers. + - Wardrobe: Purchase new collared shirts (fitted or tailored - not skinny) + - Facial hair: Trim eyebrows weekly + - Consider + - Alum block or rose water (daily skin conditioner) + - Luffa sponge or exfoliating body scrubber + - Skin Milk + - Sunscreen + - Different colored collared shirts + - Remove + - Skinny-fat, 230lbs (BMI 180lbs), no muscle (strength and stamina) + - Loose joints + - Irish Spring + - Dove (??) + - Hair (??) + - Keep + - Wardrobe + - Dove (??) + - Skin Milk + - Move more (passive -> active) + - Hair (??) + - Weekly facial exfoliation + - Plucking the eyebrow when needed, pluck ear hair when necessary, trim nose hair weekly +- Strategy or Character: + - Change + - Consider + - Keep + - Think long-term (long game) + - Don't sacrifice long-term for short-term gains + - Avoid labels + - Remove +- Philosophy (see Character): + - Change + - Consider + - Keep + - Consent, consent, consent - alternative golden rule - do that which is consensual - if it's not a hell yes, it's a hell no. + - Money is food not time. +- Mental Health: + - Change + - Consider + - Therapist and psychiatrist - friction is teledoc - being able to maintain consistency in provider while not maintaining consistency of location + - Keep + - Remove + - Self-image and -worth cognitive dissonance + - Self-recognition + - Sleep pretty okay diff --git a/content/public/legal/content.md b/content/public/legal/content.md new file mode 100644 index 00000000..556a86c5 --- /dev/null +++ b/content/public/legal/content.md @@ -0,0 +1,77 @@ +--- +title: Terms of service +dateblock: + - 20210710 Created on +--- + +# Terms of service + +{!!dateblock!!} + +These Terms of Service ("Terms") are a contract between you and Joshua Bruce. They govern your use of sites, services, products, and content ("Services") provided and maintained by Joshua Bruce. By using the Services, you agree to these Terms. If you do not agree to any of the Terms, you should not use the Services. + +Joshua Bruce may change the Terms at his discretion. If a change is material, he will do his best to let you know before those changes take effect. + +By using the Services on or after the effective date, you agree to the new Terms. + +## Content and services + +Joshua Bruce reserves all rights in the look and feel of the Services. Some parts are licensed under third-party and other open source licenses. Further, some code is publicly available under various licenses. + +As for other parts of the Services, you may not copy or adapt any portion of the code or visual design elements (including logos) without express written permission from Joshua Bruce unless otherwise permitted by law or made available in marketing materials. + +You may not do, or try to do, the following: + +- access or tamper with non-public areas of the Services, our computer systems, or the systems of our technical providers; +- access or search the Services by any means other than the currently available, published interfaces (e.g., APIs) provided; +- forge any TCP/IP packet header or any part of the header information in any email or posting, or in any way use the Services to send altered, deceptive, or false source-identifying information; or +- interfere with, or disrupt, the access of any user, host, or network, including sending a virus, overloading, flooding, spamming, mail-bombing the Services, or by scripting the creation of content or accounts in such a manner as to interfere with or create an undue burden on the Services. + +Crawling (not scraping) Services are allowed if done in accordance with the provisions of the robots.txt files. + +Joshua Bruce may change, terminate, or restrict access to any aspect of the Services, at any time, without notice. + +## No children + +The Services are not intended for children under 13 years of age. By using the Services, you affirm you are over 13 years of age. If we learn someone under 13 is using the Services, their account will be terminated. + +Individuals aged 13 to 18 must provide written consent from their parent or legal guardian to take advantage of certain Services. + +## Security + +If you find a security vulnerability with any Service, please let Joshua Bruce know. + +## Incorporated rules and policies + +By using the Services, you agree to the collection and use of information as detailed in the Privacy Policy. If you are outside the United States, you consent to the transfer, storage, and processing of collected information (including your personal information and content) in and out of the United States. + +## Privacy policy + +### What we may collect + +We may collect information about which pages you visit, information about the means used to access the Services (browser type, mobile or desktop for example); none of this information is tied to you as an individual. + +Information you send us and referral information may be tied directly to you as a user. When you use the Services, we may collect and store your Internet Protocol address in accordance with our host’s anti-SPAM policies. We may use this information to fight spam and other abuses; to personalize Services; or to aggregate non-identifying information about how people use the Services. + +### Disclosure of your information + +As a rule, we do not share your personal information. + +We will not sell your personal information. + +We may share your personal information with third parties in limited circumstances, including: (1) with your consent; (2) to a vendor or partner who meets our data protection standards; or (3) when we have a good faith belief it is required by law, such as pursuant to a subpoena or other legal process. + +If we're going to share your information in response to legal processes, we will attempt to give you advance notice so you can challenge it (for example, by seeking intervention from the courts), unless we're prohibited from doing so by law or court order. We will object to requests for information about users of our Services we believe to be improper. + +## Miscellaneous + +**Disclaimer of warranty.** Joshua Bruce provides the Services to you as is. You use them at your own risk and discretion. That means they do not come with any warranty. None expressed, none implied. No implied warranty of merchantability, fitness for a particular purpose, availability, security, title, or non-infringement. + +**Limitation of Liability.** Joshua Bruce will not be liable to you for any damages that arise from your use of the Services. This includes if Services are hacked or become unavailable. This includes all types of damages (indirect, incidental, consequential, special, or exemplary). And it includes all kinds of legal claims, such as breach of contract, breach of warranty, tort, or any other loss. +No waiver. If Joshua Bruce does not exercise a particular right under these Terms, that does not waive the right to do so. + +**Severability.** If any provision of these terms is found invalid by a court of competent jurisdiction, you agree that the court should try to give effect to the parties' intentions as reflected in the provision that other provisions of the Terms will remain in full effect. + +**Choice of law and jurisdiction.** These Terms are governed by Tennessee law, without reference to its conflict in laws provisions. You agree that any suit arising from the Services must take place in a court located in Nashville, Tennessee. + +**Entire agreement.** These Terms (including any document incorporated by reference into them) are the whole agreement between Joshua Bruce and yourself concerning the Services. diff --git a/content/public/media/web-development/progressive-enhancement.png b/content/public/media/web-development/progressive-enhancement.png new file mode 100644 index 00000000..cbfc4f92 Binary files /dev/null and b/content/public/media/web-development/progressive-enhancement.png differ diff --git a/content/public/media/web-development/reader-view.png b/content/public/media/web-development/reader-view.png new file mode 100644 index 00000000..c0aa1696 Binary files /dev/null and b/content/public/media/web-development/reader-view.png differ diff --git a/content/public/self-improvement/_the-5-ps/content.md b/content/public/self-improvement/_the-5-ps/content.md new file mode 100644 index 00000000..27fe52f7 --- /dev/null +++ b/content/public/self-improvement/_the-5-ps/content.md @@ -0,0 +1,37 @@ +--- +title: The 5 Ps +dateblock: + - 20210623 Created on +--- + +# The 5 Ps exercise by Josh Bruce + +{!!dateblock!!} + +When [8fold](https://8fold.pro) considers bringing in a new Practitioner we go through this exercise with them before they participate in an 8fold event. + +Please list your top three-to-five in the following areas: + +1. Passions: Things you do without any more of coercion or motivation; the thing itself is the reward. +2. Pensives: Things you are willing to do but usually requires a bit of outside influence. +3. Prohibitions: Things you won't do regardless of coercion or outside influence. +4. Peeves: Things others do that bother you. +5. Pedagogies: Things you are wanting to learn more about or teach others about. + +What we often find is that many of us perform our "pensives" in order to afford our "passions"; a project manager who uses the money earned to pay for their music passion. Further, the pedagogies area is often used to increase knowledge and earning capability in the "pensives" area, while people tend to want teach others about their passions; paying for courses in project management to increase salary and pay for instruments that students can use when teaching them music. + +This exercise tends to be seen as more difficult the [Motivators exercise](/highest-version-of-the-self/motivators) because you're starting with a blank slate. However, when done together, we find the two are complimentary. + +Run through the motivators exercise, then look at your top motivator and list activities that demonstrate this in the "passions" bucket. For the "prohibitions" you may decide to list the inverse of the "passions." + +Another way to overcome the writer's block that can be associated with this is exercise is to treat it more like a brainstorming exercise. Don't limit your number of responses, just list everything that comes to mind and don't edit. Once you have a fair amount of items, review regular and iterate on them. + +For me, just off the top of my head and limiting myself to one each: + +1. Passions: Helping others make peace with space and time. +2. Pensives: Software development. +3. Prohibitions: Lying - specifically, speaking what I know to be counter to reality; I may be wrong about something, but it's because I'm coming from a place of ignorance. +4. Peeves: When people assume to know my "why" or speak on my behalf without my consent. +5. Pedagogies: Learn to become a better storyteller and teach others how to design their lives. + +If you look at my motivators, most of these should not come as a surprise, given my primary motivator is purpose and my second is typically autonomy. diff --git a/content/public/self-improvement/content.md b/content/public/self-improvement/content.md new file mode 100644 index 00000000..091c8dab --- /dev/null +++ b/content/public/self-improvement/content.md @@ -0,0 +1,3 @@ +--- +redirect: /design-your-life +--- diff --git a/content/public/self-improvement/motivators/content.md b/content/public/self-improvement/motivators/content.md new file mode 100644 index 00000000..5d0689c1 --- /dev/null +++ b/content/public/self-improvement/motivators/content.md @@ -0,0 +1,3 @@ +--- +redirect: /design-your-life/motivators +--- \ No newline at end of file diff --git a/content/public/software-development/content.md b/content/public/software-development/content.md new file mode 100644 index 00000000..9aab89dc --- /dev/null +++ b/content/public/software-development/content.md @@ -0,0 +1,32 @@ +--- +title: Software development +dateblock: + - 20210619 Created on + - 20211031 Updated on +--- + +# Software development + +{!!dateblock!!} + +I differentiate software development from [web development](/web-development) in the sense that all web development is software development but not all software development is web development. Web development is software development in a specific context, with contextual constraints. + +I think I wrote my first line of code in the late [.ʼ80s](nineteen eighties) using [Logo](https://en.wikipedia.org/wiki/Logo_(programming_language)). Did it again in the mid- to late-nineties; only this time it was animated and made sound. In 1998 I made my first website. + +In the beginning I never said I was a developer; most times I still say I'm not. Back then I was strictly drag-and-drop. + +Then my first paying client told me my [.HTML](hypertet markup language) sucked and they expected better for someone with my billing. (I never did get paid.) So, I started doing more with [ActionScript](https://en.wikipedia.org/wiki/ActionScript) via [Adobe Flash](https://en.wikipedia.org/wiki/Adobe_Flash) (which was Macromedia Flash at the time). + +Flash gave way to database-driven websites using [.PHP](PHP: Hypertext preprocessor), HTML, CSS, JavaScript, and the [.MySQL](my sequential query language). + +This is when I really started looking into object-oriented development. + +I freelanced full-time for from 2007–2010. I did sites using WordPress, straight HTML and CSS, and even my own [.CMS](content management system). Around 2011 I decided to try my hand at developing an app for iOS. + +So, I started learning [Objective-C](https://en.wikipedia.org/wiki/Objective-C) and later [Swift](https://en.wikipedia.org/wiki/Swift_(programming_language)). I appreciated developing in both languages and in the Apple ecosystem in general. The app wasn't a viral success or anything, but the revenue it generated did pay for the annual developer license. Unfortunately, no one was asking for more features and no one was reporting any bugs and Apple took it off the App Store. + +In their defense it hadn't seen an update in a pretty long time (years, plural). But, it still worked for me and I don't do updates for their own sake like some others seem to. + +Now I work in [.IT](information technology) as an [Agile Coach](https://agilemanifesto.org) and [Scrum Master](https://scrumguides.org), which is pretty interesting considering I still tend to say I'm not a software developer and my college career was in fine arts, which sometimes felt like the distant cousin of the other liberal arts. (We build things instead of writing about thing, but our deliverables have roughly the same probability of becoming viable in the economic marketplace as philosophers and the like.) + +So, all that to say, if you're looking for someone who fancies themselves a hacker extreme coder who knows everything from binary through Assembly to the moon, I'm not the guy. But, I've been around the block and I've seen some stuff, I guess. diff --git a/content/public/software-development/why-dont-you-use/content.md b/content/public/software-development/why-dont-you-use/content.md new file mode 100644 index 00000000..6633327c --- /dev/null +++ b/content/public/software-development/why-dont-you-use/content.md @@ -0,0 +1,84 @@ +--- +title: Why don't you use (WordPress, PolyWorkHQ, Medium, LinkedIn, and so on)? +header: Why don't you use a framework or platform? +dateblock: + - 20210619 Created on + - 20210710 Updated on +--- + +# Why donʼt you use a framework or platform? + +{!!dateblock!!} + +Since 1998 I have always created the platform used by my website. Be it Adobe Flash, PHP, or trying to write one in Swift. Inevitably someone asks me why I feel the need to reinvent the wheel or why I think I'm "better" or "too good" for those other platforms. + +Not to sound too "get off my lawn" about the whole situation I'll say: I've tried other platforms, continue to try other platforms, and continue to come back to the ones I've built. + +I've never been a fan of WordPress and I started using it around 2005 and it inspired some of the decisions I made as I iterated on my own content management system. The biggest problem I have with WordPress is that its rapid rise in popularity turned into this odd Frankenstein's monster where every website either had to use WordPress or the problem we were trying to solve could be solved by WordPress, even if WordPress wasn't designed to do it. + +I've had two regular jobs as a web developer. In both cases, every problem or site that came across the table WordPress had to be the solution. In the early 2000s we wanted to avoid custom code to reduce the learning curve of someone coming in after me; of course, I still had to explain all the customizations we did to WordPress to make it work for the client. In 2012 at my second and last gig as a full-time web developer I would spend hours and sometimes days trying to beat WordPress into submission to give the client what they needed. This usually ended with me writing a user's manual so they could update the site themselves. The equivalent from 2015 or so would be the single-page client-side web app; think frameworks like Angular. A brochure site for a local restaurant with a menu and list of locations doesn't need all that...not a good solution for the specific problem. + +[Maslow's Hammer](https://en.wikipedia.org/wiki/Law_of_the_instrument) was in full effect and still kinda is. Turning a technology (the web) designed to be accessible to the "commoner" into something requiring complex, advanced knowledge. + +This desire to make the web accessible to more people so they could create their own content saw a boom in platforms where those who didn't want to learn the syntax of HTML and the technology of transferring files could post their own content. [Twitter](https://twitter.com), [Facebook](https://www.facebook.com), [MySpace](https://myspace.com), [LinkeIn](https://www.linkedin.com) and other social media platforms started dominating the landscape. Of course, many of these came after the push by developers to create software the "commoner" could install on their own servers to generate their sites; [WordPress](https://wordpress.org), [TextPattern](https://textpattern.com), [Movable Type](https://www.movabletype.com), and countless others. + +There seems to be a resurgence of platforms and applications coming in now as the technologies have stagnated or the ethics of the creators is being challenged. + +I spent the first 10 years of me on the Internet with a website I developed and curated. I spent three years without a website at all (while getting paid to develop websites for other people). And I've spent another 10 years looking for a platform and audience in applications developed by other people. + +The thing that keeps me coming back to a custom build addressing my specific needs is that I'm in control. + +Don't get me wrong, I'm not a very command-control person, in fact, quite the opposite. With that said, I want to be able to create my own [terms of service](/legal) and abide by my own ethics, I don't want you to feel my content is forced upon you by someone else, and another big thing is I don't want to be tightly coupled to someone else's product or service (I want to be relatively self-sufficient). + +## Solving my own problems + +As of the [2021 build](/web-development/2021-site-in-depth) of this site, I no longer use [Laravel](https://laravel.com); for reasons described in [this article](/web-development). In short, I didnt use a lot of the features provided by Laravel. + +I don't use trackers or analytics that can keep tabs on my visitors; I don't care how old you are (for the most part) or any other demographic information. Actually, for the most part, I avoid analytics altogether because it could slow down your experience, even the one I wrote. + +The Internet is fast. We're just not building like we had to back in the day when it was literally slow. + +## Don't force feed + +There's a lot of advice out there on how to grow an audience. One of the most prolific and specific pieces of advice is to generate a shit-ton of content. + +> You should be posting four or five times a day! + +I feel like this advice comes from the more eyeballs mentality that originated with ad revenue. More eyeballs equals more money to the platform owner. Of course, if you're posting to someone else's platform, they're making all that money. Not only that, but this advertising model has led to the creation of the feed and trying to get people to scroll forever on the page. + +If I want to appear in your feed, I need to produce more content than the next person. Specifically, I need to create more content you are willing to interact with than the next person. + +I remember when I was still publishing on Medium I started talking about productivity and [self-improvement](/self-improvement) and was just starting to build an audience. I started following some other folks in that niche. They were putting out daily articles; using the following formula. + +1. Monday: Post article about life hack 1. +2. Tuesday: Post article about life hack 2. +3. Wednesday, Thursday, Friday: Post articles about life hacks 3, 4, and 5, respectively. +4. Saturday: Post article about "5 great life hacks" that summarized and regurgitated the previous five articles. +5. Monday: Post article about life hack 6. +6. Tuesday... +7. Saturday: Post article about "10 great life hacks" that summarized and regurgitated the previous 10 articles. + +Quantity. Quantity. Quantity. + +That's not how I've ever wanted to roll on the Internet. However, I felt what happened as a result; lowered engagement, less discoverability of the content I was putting out, and so on. So, I stopped posting to Medium while I could figure out how I wanted to do content in this ever changing medium called the Internet. + +The two big things I didn't want were: + +1. I didn't want to repeat content. +2. I didn't want to leave content up that was stale. + +That's when I came to the three content type model. + +## Decoupling + +The longer you let someone or something else do things for you, the more coupled you will become. Something I'm seeing a lot of in the software development space is "modernization" efforts. + +Moving clients from database A to database B or micro-services instead of monoliths. + +To pick on WordPress again, it used to be that WordPress would create a whole database architecture for you (don't know if that's still the case). The more content you created, the more it was tightly coupled to that database architecture and, by extension, to WordPress. It got to the point that many of the other content management applications had scripts that would perform database migrations from WordPress (or other popular tools). + +Of course, you're basically getting a divorce and immediately remarrying someone else with similar baggage. + +As of this writing, this site doesn't use a database, it uses the file system; kind of like a traditional website build from 1998 or, if you're into static website generators, it's like that from a content perspective. From a presentation perspective, there are a few hundred lines of site-specific code I maintain. For the most part they're plain PHP or using libraries I also write and maintain in a similar "only solve problems you have" spirit. + +The most annoying part for me right now is getting all my content wrangled back into this site. Beyond that, the site's just leveraging what the Internet is good for. Receiving requests and returning bit steams; mostly in the form of plain-text. diff --git a/content/public/web-development/2021-site-in-depth/content.md b/content/public/web-development/2021-site-in-depth/content.md new file mode 100644 index 00000000..4708139c --- /dev/null +++ b/content/public/web-development/2021-site-in-depth/content.md @@ -0,0 +1,167 @@ +--- +title: 2021 site refresh in-depth +dateblock: + - 20211031 Created on +--- + +# 2021 site in-depth + +I think I'm going to call this one the quick and considerate update. + +The main focus of this site is text-based ramblings. The Internet has come a long way since I started. My understanding of it and [software development](/software-development) has also come a long way. + +## Considerate (lots of firsts) + +Most operating systems and browsers now support user preferences. To be fair, users have had the ability to create custom style sheets for a little while now (no extensions necessary): + +- [Safari](https://apple.stackexchange.com/questions/176831/change-css-style-in-safari-on-all-sites). +- [Firefox](https://support.mozilla.org/en-US/questions/841578). +- [Internet Explorer](https://support.microsoft.com/en-us/topic/internet-explorer-11-crashes-when-you-use-a-custom-style-sheet-b343676b-26c8-0c2d-624e-cc61a4cea53e), though seems like it could be broken. +- Edge and Chrome would be better suited using an extension from what I could find. + +Most modern browsers also have what is referred to as a reader view where the browser will take your content and apply the browser team's styles to it in a separate modal presentation. + +![Screenshot of this page in reader view in Safari](/media/web-development/reader-view.png) + +Users can also decide to turn styling off or go without JavaScript. Reduce animations and motion. Users may also be looking at the site on a screen the size of a watch less than a foot away or a 60 inch television from 50 meters. Could be the latest browser running on the latest hardware, or, something old on both scores. + +The questions are: What experience should they be able to have, if any? What experience do we want them to have? + +Believe me when I say, the hardest thing about web design and development is realizing that if there is only *one* way to properly experience what you've created, it could be problematic. + +So, [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement) for the win. + +### No [.CSS](cascading style sheet) or [.JS](JavaScript) first + +The core functionality of this site is the ability for a user to navigate and read content. Most, if not all, considerations should be made from that lens. + +![Screenshot of this page without styles of JavaScript](/media/web-development/progressive-enhancement.png) + +As of this writing, as long as the browser the person is using understands some [.HTML](hyptertext markup language) that's been around since version 1, they'll be able to navigate, read, and see the images. + +This does raise the question of how do we determine what features we should and should not use. Otherwise known as what browsers will we actively support? + +1. I will actively support browsers that simply don't have CSS or JS available to them. (Support isn't the same as catering. Support just means they'll most likely be able to see something. The Internet is insanely backward compatible. See the original [Space Jam](https://www.spacejam.com/1996/) movie website from 1996—it still works.) +2. I will neither support nor cater to any version of Internet Explorer. If a user reports the site isn't awesome on [.IE](Internet Explorer), I will kindly suggest they upgrade as it is scheduled to reach end of life around the [middle of 2022](https://docs.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge) (though compatibility mode will keep it alive for a long while after). +3. I will actively push forward the adoption of various web technologies by using them: specifically, [CSS Grid](https://caniuse.com/css-grid) and [Custom Properties](https://caniuse.com/css-variables). This means, according to the Can I Use website, this site will be roughly similar on most desktop browsers updated since 2017 and most mobile browsers. + +That's pretty much how I chose. + +It's conjecture in that I didn't ask users, however, I don't know any of my users at the moment. My guess would be friends and family and I know we're all pretty good about updating software. + +### Reduced motion first + +It may be hard to tell given how much animation and transitions play in modern interface design, but some people really don't do well with all that motion. + +Therefore, I want to be considerate of that. In the [style sheet](/assets/css/main.css) there is a media query that says `prefers-reduced-motion`, at least as of this writing. By default, any CSS transitions that could cause a lot of movement are placed here. + +We might call this: reduced-motion-first design. + +It's not because the user may not have a browser that supports this type of thing, it might just be they don't do well with it and would be annoyed to the point of bailing. + +### User preferences first + +If a user prefers dark color schemes, I want to respect that. Again, looking at the style sheet, you'll see a media query that says `prefers-color-scheme`. By default the site uses a light theme and modifies what it needs to in order to go into dark mode. + +[Firefox](https://www.mozilla.org/en-US/firefox/new/) has some good accessibility tools built in. And I use them to check for contrast, tab order, and other things. + +It's worth noting that I could have made this a toggle a user could turn off and on. If I did that, I would still want to show up respecting their system preference. Progressively enhancing into the toggle behavior would take a fair amount of additional effort and complexity—sessions, form submission, and then JS. + +So, it's in the backlog, it's marked as low value, medium effort and not worth it at the moment. If people start asking for it, the value might go up. However, until the perceived value outweighs the effort, it won't happen. + +### Mobile first + +The CSS is designed to define what the site should look like on mobile. Media queries will be used to modify specifically for the other screen sizes. As of this writing, there are no media queries related to screen size or resolution. + +## Quick + +There's a joke about football coaches who are "old school." These coaches believe the forward pass is a trick play. This is how I feel about caching. Or, at least developing a caching strategy. + +### Caching strategy + +The Internet would be crazy slow if it wasn't for caching, of course, but still. + +In short caching is copying a file stored in one place and storing that copy somewhere closer to the person who needs it. + +We can take the output of any process and store that somewhere. The fastest websites are still the old school websites (like the Space Jam one from above). + +We can also ask the browser to cache things on your device for a certain period of time. If you've ever reached out to support and they told you to: + +> Clear your cache and cookies. + +They're trying to overcome the problems with caches. As of this writing, I don't use any strategy in my code to generate a cache. Instead, I leverage the cache controls of browsers. For example, I think the page is cached for about 10 minutes, any media is cached for a week, and any assets are cached for 30 days. + +When the content is on the server closest to me, that means folks in Tennessee might get the response a bit faster than someone in Texas. However, once someone in Texas requests the content, it will most likely be cached closer to them, which means future requests from Texas will also be closer and quicker. + +This means I have to be mindful of how long it takes for everything to process. On my local computer (the one I use to develop and write the content), it's about 100 milliseconds; one-tenth of one second. + +So, chances are, you will not need to clear your cache to see the latest and greatest from the site. Further, performance increases made in languages and the servers managed by my web host will automatically benefit me without any overhead or consideration on my end—it just works. + +### Optimized files + +The screenshots above are in the [.PNG](Portable Network Graphics) format, which is pretty big compared to some other formats. I run them through an optimizer. The optimizer can reduce their size by around 50 percent or more. This makes the graphics load faster. I also plan to use some more future-oriented formats, which are designed more for the Internet and smaller file sizes. + +My server will also compress certain file types before they are transferred to you. Because my host handles this for me it means there's no additional overhead for me. + +One of these files that gets compressed before being sent should be the CSS. Since switching to using CSS properties almost exclusively to define my "design system," I've noticed a slight improvement when the CSS is compressed. Once you receive the compressed file, it will be uncompressed and displayed for you. This all happens automatically. + +Because I use the libraries I do for generating the HTML, it's actually minified, which is to say stripped of extra whitespace that's used to make it easier to read. For a non-HTML example, look at the [minified version](/assets/css/main.min.css) of the CSS served with this page; as opposed to the [non-minified version](/assets/css/main.css) I've been linking to up to this point. If you look at the raw HTML source for this page, you'll notice it's mostly one continuous line of text. + +### Let the browser and server be a browser and server + +That's a long headline. + +For [modern web development](/web-development/modern-web-development) we have a tendency to get in the way of or replace the capabilities we get for free from the browser-server relationship. Instead of clicking a link and the browser responding, we jump in the way using JS to see if we need to do something before handing it to the browser to then do what it would have done anyway. Before delivering a response to a request on the server-side, we jump in the way and reroute things (guilty of both). + +We want to minimize how much we use our code to replace or intercede with the browser and server. + +Basically, our modern web development code is a helicopter parent on the defensive. Are you sure want to click that link and navigate away from this page? Here, let me check first. + +When client-side application development started gaining steam (and even back in the Flash days), the mantra was: Don't break the back button. + +This was because developers were using JS to interpret address (route or header) requests and not respecting the browser page stack. You navigate a few times, then hit the browser back button and lose all the progress up to that point. Did that cause these developers to stop breaking the back button? + +For some, sure, but others just created their own duplication of the browser navigation (back, forward, and even refresh)…thereby replacing and taking on the burden of writing a browser as well as a web application. + +## Shareable + +No, this doesn't mean integrations with social media (being able to like or love something on my site, which posts to your social media for you). Instead, we're talking about human-readable [.URLs](uniform resource locators) and inline anchor tags. + +Safari has a [sharing capability](https://support.apple.com/guide/safari/share-or-post-webpages-sfri40722/mac) built in. Click the button, send the URL via email, message, or something else entirely. For browsers that don't have this capability, you can still copy-and-paste. + +When someone receives it, they should be able to read it and have a pretty fair idea of what they're going to get. + +This drive to make things shareable means the information architecture of the site needs to be pretty tight. + +My general rule for the site is that content should not be more than 3 levels deep. + +The hash signs (#) are links directly to that header. If you click it and share that URL, the person you're sending it too will come straight to that headline, if it's available. + +I also have a ticket to add the tags necessary to support the social cards as well, just not a hight priority at the moment. (You know, you share a link on social media and there's a fancy card presented.) + +## Content is still king + +I appreciate the design department at Apple. (I also appreciate other departments, but we'll stick with marketing and design.) When you go to their website, there is no question what they want you to do and look at: the products. + +Press releases? That's like a full page scroll and two clicks away. If you didn't know where to look, you wouldn't see it. + +Investor relations? Same. + +For this site, it's the articles. Period. + +The menu isn't even at the top of the page. + +Navigation, copyright and other legal, along with support and social (coming soon) are all secondary to the content you're reading, the related and linked to content, and the external resources and references. + +The design reenforces this; in my opinion. + +## Deploying updates + +Right now the code and content is all stored on GitHub. I've cloned them to the server. My code editor has a terminal and let's me automatically sign into my remote server. two commands and a password later, the site's pretty much updated. + +Eventually I'm looking to add a script that will automatically do the update for me (continuous deployment) but, for now, it's not annoying enough. + +It's relatively instant and the site usually isn't down while I do it. Just the changes that were made since the last update are brought over. + +Takes less than a second for me to deploy the site and content. + diff --git a/content/public/web-development/content.md b/content/public/web-development/content.md new file mode 100644 index 00000000..3638d0bf --- /dev/null +++ b/content/public/web-development/content.md @@ -0,0 +1,110 @@ +--- +title: Web development +dateblock: + - 20211031 Created on + - 20211105 Updated on +--- + +# Web development + +{!!dateblock!!} + +I differentiate [software development](/software-development) from web development in the sense that all web development is software development but not all software development is web development. Web development is software development in a specific context, with contextual constraints. + +Generally speaking, when I do develop software, I do it for the web. Further, I take a user down approach. (I never reach, "the metal".) However, after poking around and looking I can say that, at a reasonable level of abstraction, developing software feels the same. + +## This site (2021 update) + +This site separates the application logic from the content, style, and interaction. + +The [site is open](https://github.com/8fold/site-joshbruce.com) and available on GitHub. The [content](https://github.com/joshbruce/content-joshbruce.com) is also open and available; though I am reserving full copyright. + +I will say, as with many of my creative endeavors this was kind of a reactionary thing. I keep hearing about low-code and no-code solutions and I would submit that it doesn't take a lot of code to make something work these days. + +As of this writing the code running this site weighs in at 34 kilobytes. To put that in perspective, I just took a photo with my iPhone 12 mini. No special settings; just the default settings. That picture is 2,000 kilobytes. Two pictures on my phone are over 100 times larger than the code I wrote that generates this site. + +Now, to be fair, that doesn't include the supporting players, if you will. The packages I also use to help out. Those way in at a total of 35,000 kilobytes. With that said, a fair amount of that isn't actually user-facing. + +34 kilobytes of code. That's receiving the request. Determining how to respond to the request. Querying the local file system, if necessary for text content and file transfers. And rendering the [.HTML](hypertext markup language) response. + +I'd say that's pretty "low-code." + +You can read more about the 2021 update [over here](/web-development/2021-site-in-depth). + +November 5th update: I just realized [JAMstack](https://jamstack.com) was a thing and I find it funny from the perspective that it's kinda where we're heading. Pretty sure this was originally the idea, precursor, or concept around server-less, which I admittedly didn't pay too much attention to. I'm listening to an audiobook on the subject right now and they seem to be making some of the same arguments and points I've been making and brain-dumping here. + +## And before + +This is the brain-dump history. You've been warned. (All the years are rough.) + +### 2019–2020 + +The site at that time focused on productivity coaching. And had two design iterations in that time. I've never viewed a website as being necessary to operate professionally in this world. Contrary to what it may feel like, most people on this planet don't have a website and don't participate in social media. + +Some are too young. Some don't have access to the technology and infrastructure. And others simply can't be bothered. + +While I had been using [Laravel](https://laravel.com) I started feeling weird about it. I wanted to be decoupled from everything. I wrote a wrapper for [.PHP](PHP: Hypertext preprocessor) itself; that's how decoupled I wanted to be. Now, I can say I'm pretty well decoupled from most of the little things. + +If my host goes out of business tomorrow, I can move the files somewhere else without much issue. Granted my host has been my host for roughly 15 years at this point. + +I use Git for source control and the site and content are stored on GitHub. So, I have local copies and if GitHub dies, I'll be able to move the code somewhere else. + +Basically, I have catastrophic failure covered and, as of this writing, this website doesn't help feed me, so, if it goes down I probably wouldn't notice and neither would most of the planet. That's not a knock or self-deprecating humor, that's just the facts at hand. + +I started thinking about micro-objects and the proxy and facade pattern as a way of decoupling myself from dependencies. I used service providers exclusively when developing with Laravel because I could put all my code there and leave all the Laravel code "over there." Then I realized my controllers didn't *need* to inherit from the Laravel base controller. I also started pealing back more and more of the "how are they doing this" layers to realize just how little of Laravel I was using for most of my work. + +Stopped using databases in favor of markdown and front matter; so, not using Laravel database migrations or [Eloquent](https://laravel.com/docs/8.x/eloquent). Was using the [CommonMark](https://commonmark.thephpleague.com) package from the league of extraordinary packages for most of the HTML and have my own libraries for the rest; therefore, not using Laravel [Blade](https://laravel.com/docs/8.x/blade). Opted out of creating admin panels—just use source control services or an [.FTP](file transfer protocol) client; so, no [authentication layer](https://laravel.com/docs/8.x/authentication) needed. And I don't take payments online from a diverse audience, so, no need to integrate with multiple payment providers using something like [Cashier](https://laravel.com/docs/8.x/billing) and I don't really have the inclination to write the integration; 8fold is using Wave's invoicing mechanism and I'm happy to pay the few cents each time a transaction is made. I was barely even using the router; had three routes and they accepted *any* [.URL](uniform resource locator) and the controller processes that path. + +Now, could I find excuses to *need* all that stuff? Absolutely. + +As someone who enjoys solving their own problems and building stuff online is it hard to stop myself from doing it for its own sake? You (probably) have no idea. + +Am I happy that my ability to upgrade my code is less dependent on other people? One hundred percent. + +Still recommend Laravel to anyone. Hands down. With that said, I went back to my roots on this one: + +> If you aren't going to use half of the things provided by a library or framework, you better have a damn good reason for taking it on. + +### 2018 + +(By the way, I'm totally cheating. I'm too much of a time lord to actually remember any of these dates. But the [Wayback Machine](https://web.archive.org/web/20180330105911/https://joshbruce.com/) is pretty awesome.) + +This was one of those odd moments when I thought I wanted to embrace my software developer moniker. I believe I had also become recently unemployed and threw a site together real quick. Looks like I used a modified [.USWDS](United States Web Design System). + +### 2010–2017 + +I actually didn't own the joshbruce.com domain until 2010. And I didn't do much with it for this seven year span of time. + +During this time I spent more time trying out other people's platforms than working on my own. Embracing that Web 2.0 consumer-created content life, I suppose. + +I always had concerns regarding using other people's platforms. The main concern being that I didn't own the terms of service or the business operations. So, I never knew what would happen next. + +Would the company decide to do some interesting things with paywalls once they became popular enough to start being exclusionary in their practices, for example? + +Not that it every happened, of course. (Totally happened. Or at least felt like it from my perspective.) + +Based on my socisl media posts, I'm pretty sure I started using Laravel during this time. I appreciated Laravel a great deal. Think I even mentioned once that it felt like what my initial attempts could have turned into. In other words, it felt natural to me to use. Intuitive. However, I also acknowledge that while that may be the case for me, it's most likely not the case for everyone. + +### 2007–2009 + +We could probably call this the Dark Ages for me. + +I took my site down. I wasn't really on other platforms much. + +And yet, I was freelancing full-time as a web developer and business consultant. + +One exchange I'm fond of reflecting on was asking a client: Why do you think you need a website? + +The response was pretty typical: everyone needs a website these days, it projects legitimacy, and similar. + +My response was: You're about to hire a web developer who doesn't have a website. How did that happen? I'm willing to take your money to make the site, don't get me wrong, I need to eat; it's not what I recommend you do though. Your money would be better spent on branded t-shirts and giving them away to local shelters and doing more community outreach like that. You're a local brick-and-mortar. Selling things online is a long ways off for you. Pay me a few hundred dollars for this advice and take the other thousands of dollars and do the community outreach bit. + +They paid for the site. + +### Prior + +Pre-college. Had some client work. And had a standard issue blog, like most internet people at the time. I developed my own CMS and was using that. It wasn't horrible for the time. + +Of course, I found an iteration of the code I found somewhere and thought: Cool! I could totally use this as the foundation for my next website iteration. + +Then I opened the authentication code. And closed the file faster than someone closing the closet door when stuff starts falling off the top shelf. Reminding myself once again to always be humble. diff --git a/content/public/web-development/modern-web-development/content.md b/content/public/web-development/modern-web-development/content.md new file mode 100644 index 00000000..165f63e1 --- /dev/null +++ b/content/public/web-development/modern-web-development/content.md @@ -0,0 +1,231 @@ +--- +title: Modern web development +dateblock: + - 20211018 Created on + - 20211105 Updated on +--- + +# Modern web development + +{!!dateblock!!} + +The term "modern" is an odd one, freighted with baggage. In the context of web development it seems even stranger given that I’m older than the public web. + +In some cases the term modern feels rather ageist when it’s used to say, “I won’t use anything from a certain number of years ago”—the new shiny is better than old reliable. I remember using [CSS Zen Garden](http://csszengarden.com) as a reference to demonstrate how flexible minimal, semantic markup can be due to the use and ubiquity of [.CSS](cascading style sheets). The retort from the development team at the time was that CSS Zen Garden was an "old" site. + +Technological ageism. + +In other cases "modern" seems tightly coupled to an approach or technology stack. The single-page client-side web app, use of microservices, and over-the-wire calls. + +I'm sure there are more uses and think this is enough to suffice. + +The web is built on an old concept, in my opinion; input and output or I/O. A human user interacts with a client. That interaction is turned into a request. That request is sent somewhere. That somewhere then returns a response. + +A static website for example: + +1. The human enters an address into a browser (client) and hits enter. +2. A request is made to the internet service provider who will pass it along to where it needs to go (this is a simplification). +3. The request arrives at a computer somewhere (server). +4. The server takes off the domain or [.IP](internet protocol) address from the path requested and looks for a specific file or a fallback (or default) at that location. +5. The server then builds a response (some common responses are listed): + - An appropriate file is found and everything is "ok"; a [200 response](https://httpwg.org/specs/rfc7231.html#status.200). + - An appropriate file is *not* found; a [404 response](https://httpwg.org/specs/rfc7231.html#status.404). + - An appropriate file is found, but you lack the credentials to get an ok response: a [403 response](https://httpwg.org/specs/rfc7231.html#status.403). + - An appropriate file is found, but wants to send you somewhere else instead: a [300 response](https://httpwg.org/specs/rfc7231.html#status.300). + - The server doesn't even know where to begin really; a [500 response](https://httpwg.org/specs/rfc7231.html#status.500). +6. The response is sent back to the client for interpretation and display. + +This is the basis for any style of communication, internet or otherwise. Each word and sentence you read can be interpreted as a request from me, the author. The request I'm making is for your understanding. If I use words you know, then your response will be a 200. If I use a word you don't know, then the response will be a 404 (word not found error). If I use or reference an inside joke you aren't familiar with, it will either be a 404 or 403. If I link to something and you follow the link, that's a 300 response. If the words somehow "break your brain," that's a 500 response. + +In the ’90s, you kinda had to be tech-savvy and “in the know” to be creating and consuming content online, and it felt like it. Consider the [.URLs](uniform resource locators) of the time: + +For example: + +1. `http://www.yourdomain.com/home.html` +2. `http://www.yourdomain.com/about.html` +3. `http://www.yourdomain.com/contact.html` + +At least the domains were human-friendly and not straight [.IP](internet protocol) addresses. There are only two human-friendly parts of the routes listed: the domain and the file name (at least the filename without the file extension—everything else is to help the computer understand what you’re trying to do). + +I’m not an historian beyond my own life, so, this isn’t meant as a literal retelling of the history of the Internet and I recognize that there’s a big difference between when something started and when I became aware of it. + +Three things seemed to happen; in no particular order. + +First, dynamic languages and template engines: [.PHP](PHP: Hypertext Preprocessor), [.ASP](Active Service Pages), and so on. We called it dynamic because you didn’t need to change the [.HTML](hypertext markup language) file to change the content. We also needed to tell the server, "Hey, this isn't HTML!" So, you started seeing routes like the following: `http://www.yourdomain.com/about.php`, which sucked because, if you didn't know the underlying technology, you didn't get to see the site. + +Second, more sites started taking advantage of the default or fallback file option. Name a file `index` and put it in a folder. If the request received doesn’t have a file name and extension, the `index` file is used. This led to the creation of deeper folder hierarchies, each with one file in them. So, instead of a folder with four files, we had three or four folders with one file each, resulting in the following routes: + +1. `http://www.yourdomain.com/` +2. `http://www.yourdomain.com/about` +3. `http://www.yourdomain.com/contact` + +Before this strategy, if you wanted to change the technology of your site, say, from ASP to PHP, you had a serious problem. With the folder-based strategy, the server does the heavy lifting and doesn’t expose the underlying technology. As a developer you could change your tech-stack whenever you wanted to without worrying about breaking your routes. + +The third thing is the `www` bit. In the beginning `www` was used to distinguish between the Internet as a whole and some internal server network (extranet versus intranet, respectively). Again, this was annoying from a user experience perspective. As we progressed, it became possible to remove the `www` from the route (the "extranet" became the default for browsers): + +1. `http://yourdomain.com/` +2. `http://yourdomain.com/about` +3. `http://yourdomain.com/contact` + +Arguably, secure-by-default is becoming an integral part of the web, which changes the `http` to `https`. + +The route is the keystone of the Internet. And I would say this route style and format is the basis of modern web development. + +I witnessed this evolution of routes over the course of a few years. + +The promise from an HTML-perspective was [semantic markup](https://www.w3.org/standards/semanticweb/); starting around 2005 (at least that's when I started hearing more about it). We still haven't got there though. Many sites I visit still default to using `div` and `span` for block and inline elements, respectively, outside of content. Not a lot of `article`, `main`, and `setion`, for example. + +I first heard about [microdata](https://html.spec.whatwg.org/multipage/microdata.html#microdata) and microdata vocabularies around 2009. This let you put attributes in your HTML elements to help further describe the content. For example, if you view the source of this page, you should see that the article tag uses a `typeof` attribute with a value of `BlogPosting` and a `vocab` attribute with a value of `https://schema.org`. This means I'm using the microdata vocabulary from [schema.org](https://schema.org/) and a computer reading this file can better understand my intent; as long as it can read [.XML](eXtensible Markup Language) (specifically HTML) and can understand the schema.org vocabulary. The navigation is wrapped in a `nav` element, describing the intent of that area of the page. This article is wrapped in an `article` element, defining the intent. The microdata further describes my intent. I could put the microdata at the top of the page using [.JSON-LD](JavaScript Object Notation for Linking Data); however, I would prefer to put it in the HTML itself because the interpreter doesn't need to know [.JS](JavaScript) to interpret the intent, thereby, being technologically agnostic—anything that can parse XML (or a plain text string) can interpret this page. + +## 1998 + +I used a free [.WYSIWYG](what you see is what you get) editor provided by my [.ISP](internet service provider) to create a website. + +Think of this like Wix or similar. + +Tools like these made the Internet feel accessible. Anyone could easily create content and put it somewhere. Not only that but people using assistive technologies could experience the same thing; it’s pretty difficult to make an inaccessible website. + +## 2000–2004 + +Bought my first domain name and got a “real” host. + +Speaking of making sites inaccessible, I started developing sites using Flash. I appreciated it, for the most part, the site was the same regardless of browser. I was really hung up on the “pixel perfect” thing and with the browser wars going on it seemed impossible. + +Flash was my first introduction into a front controller, or single-page app. + +You delivered a single page and loaded a multi-faceted experience. No page refreshes. No delays between requests. Often broke the back button and caching was a problem more than a solution. + +Think of it like Angular a decade before Angular. + +What sucked about Flash was you had to load the entire site at once and there was only the one route to share with someone. If I wanted you to see a specific portion of a site, I had to tell you how to navigate there and you’d have to wait for the entire site to be downloaded even if you were only going to a small sub-area. And heaven forbid the creator changed the navigation before you got there. + +Eventually things advanced to the point we could request images, text, and other files when we needed them instead of it all being part of the initial download. We were also able to use JS to update the routes using [fragments](https://en.m.wikipedia.org/wiki/URI_fragment) (hash). Now I could keep my assets on the server and “make calls” to retrieve them when needed; think of this like a web [.API](Application Program Interface) call in "modern" terms. Further, you could send me `http://yourdomain.com/#about`, load a minimal experience to let me know something’s happening grab the route and determine which view I was supposed to see. + +Again, think Angular and similar single-page client-side web app solutions as they were up until around 2015. Or Gmail as of October 2021: + +``` +https://mail.google.com/mail/u/1/#inbox +``` + +Of course, Gmail isn’t Flash, it’s JS. + +## 2005–2007 + +I created my last Flash site in this time. It was the most “modern” iteration I made. The initial download was around three kilobytes. From there you could view almost 100 pieces of artwork in my portfolio along with my résumé. + +During this time the prevalence and popularity of the single-page server-side web app also seemed to be taking hold. Our routes turned into a jumbled mess made for computers: + +``` +http://yourdomain.com/?p=123&q=hello +``` + +The idea of a “front controller” is basically telling the server, “No matter the route, use this single file instead. I’ll figure it out.” Basically we, as developers, are taking on the burden of the server without becoming server administrators. + +[WordPress](https://wordpress.org) coupled with [Apache](https://httpd.apache.org) was the first combination I saw with "pretty Permalinks." Which is to say, developers were able to write their `index` files in a way that allowed them to build a coherent response from a human-readable route. + +I learned how the front controller worked (`.htaccess` files) and this approach along with the idea that the route was the keystone became the foundation of the first [.CMS](content management system) I ever built. + +Complaints and concerns about Flash were also on the rise. Meanwhile browsers were less finicky than they had been in the past as we pushed for simple, semantic markup. I was also a follower of those who were pushing the importance of accessibility and speedy delivery of content. + +In 2007, I took my sites down. + +## 2007–2010 + +During this time I worked full-time as a freelance web developer. + +For me, this was the Dark Ages. I started freelancing as a web developer and removed most, if not all, of my content from my personal sites. I barely used the newly coined "social media" platforms. My favorite conversation with my small business clients usually started with the question: + +> Why do you think you need a website? + +They would give me reasons, which usually boiled down to, it legitimizes you as a business. To which my response was usually: + +> You're about to hire me to do this work. I don't have a website. How does that fit into your rationale for needing a website; especially given you are small, local, brick-and-mortar shop. + +During this time it was about being discoverable and being on social media platforms. Some websites I had visited regularly changed to a single page with links to various social media platforms. + +[[.AJAX](Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) (first seen in 1999) started becoming more prevalent as a concept for interactivity on the Internet. A user clicks something, but instead of moving to a different page, a sub-request is sent to the server and a response is delivered. JS is then used to interpret the response and update the HTML. + +"Modernizing" websites was about taking static sites and converting them to database-driven, dynamic sites that users could "update themselves." We called them dynamic because the content of the page could change without modifying the code running the application or individual HTML files. + +This was also the time that I started developing my view of how the Internet works and the adoption curve. + +There is only so much we can do with the native elements of HTML and CSS; so, we use non-core elements to push the possibilities. Eventually, many of those possibilities become part of the core elements for the web. When that happens, it's our responsibility to deprecate those former solutions in favor of the new standards as much as is practical given our constraints. This is what it is to modernize. However, if the drive is to always be "modern" then you can often find yourself in a bad position as evolution may not select your version of "modern" for the future (see Flash in 2010). + +This was the time of JS libraries like jQuery, which simplified the development of client-side interactive elements. However, because it only manipulated the attributes of HTML elements, the performance often left something to be desired. + +Bandwidth was still an issue in the [.US](United States) and the concept of lazy-loaded assets became somewhat popular. Is that image below "the fold"? Then don't load it yet, we'll do that later by calling the server ourselves. (Again, taking more responsibilities from the browser and server and putting it more on our own code.) + +The barrier to entry of creating content online for the world to see was lowering. With minimal cost or for the cost of personal information and looking at ads amongst the content, just about anyone could create content online for public consumption. + +HTML and CSS specifications started gaining more ground and becoming more formalized. This was the time of the [second browser war](https://en.wikipedia.org/wiki/Browser_wars). The dominate browser at the time was [.IE6](Internet Explorer version six) and it was so horrible at being compliant with the specifications that it earned the nickname Internet Exploder, because its use caused the internet to explode despite the page being otherwise perfect from a specification perspective. Apple (via [Steve Jobs](https://en.wikipedia.org/wiki/Thoughts_on_Flash)) turned its sights on killing Flash. + +Some of the web development sites I frequented would display banners urging you to use a different browser for the best experience because the designer, developer, or both would no longer make the time to account for IE6, [IE6 must die](http://www.ie6death.com). + +JS was picking up even more steam and for good reason. With the trifecta of the web: HTML, CSS, and JS. I can make a fully interactive and dynamic website with no more tooling than a browser and a plain text editor. + +No server. No domain name. No host. No database. + +Seriously, if you want to "do web development" you can crack open Notepad (Windows) or TextEdit (macOS) and get started; both come pre-installed on both operating systems. Granted, that's always been the case. + +When I worked at a call center I used to develop HTML layouts between and sometimes during calls. I worked there from 2001–2007. + +## 2010–2013 + +The idea of breaking a page or experience into fragments and loading them through delayed server requests really started taking off. + +"Modernizing" started to mean moving to [microservices](https://www.martinfowler.com/microservices/) and single-page client-side web apps to solve the "Does it scale?" problem. + +Server-side solutions and languages felt like they had fallen into disrepair, were slow, and had gotten a bad rap; despite WordPress still being the dominate survivor of the CMS wars. One of my favorite compliments around this time (actually 2015) was that my PHP code wasn't like any other PHP code the developer had seen because "I can read it." (Granted, if they had seen my code from 2007–2010, they might have felt different.) + +I worked full-time for a year as a front-end web developer. We mainly did brochure sites. Some sites required so much customization of WordPress I found myself beating WordPress into submission as much as I could to make it easy, then writing instruction manuals on how the clients could update the content themselves. + +During this time native app development started to increase with the opening of the App Store to more developers. I started learning Objective-C and developed an app for iOS. + +Of the two, I appreciated native app development the most, specifically for the Apple ecosystem. + +There was also the beginnings of a "back to basics" movement for web development with the creation of "modern" static site generators (similar to FrontPage and Dreamweaver from years prior, only with an automated build step). These static site generators work like dynamic sites, but convert the pages to static HTML instead of using processing time to dynamically generate (render) the page. + +## 2013–2016 + +During this time it seemed like everyone I talked to about web development was looking toward what they considered the bleeding edge of modern. + +Modernizing "legacy" websites became a more dominate industry unto itself. Creating new sites also required this "modern" means of development. + +I remember going to a locally owned restaurant website. I watched as the site instantly loaded a blank field of color, then the spinner-of-doom appeared to let me know I would need to wait while the API calls were made and the content returned (basically doing the first round trip a second time). It was a single page and displayed the menu for the restaurant. The design was lackluster and not complex at all. My cynical side said, "They spent all their budget on 'future-proofing' a site I could have coded in a day." + +I started working on another program as a "professional" consultant. They too were looking to do "modern" web development after being sued for their site not being accessible. We talked about APIs and micro services. Angular was just going from version one to two. Teams wanted to be able to use whatever technology they wanted to in order to deliver what was asked of them; regardless of how difficult it would be to maintain and find the people with the necessary skills to do so. + +In one case in particular we needed a form that someone would submit, it would be reviewed, and then approved for publication. The proposed solution was to integrate with GitHub and use their APIs and document review workflow. All this despite the infrastructure being in place to do this "the old-fashioned way" in less than a week. + +I learned and grew a lot during this time. I still remember looking at the Product Owner and saying, "I'm just gonna build it." When he asked me what I meant, I said, "I'm going to build what we've spent the last two months asking them to build." He asked how long it wold take, to which I responded, "I'll have however much I can have in two weeks or less." + +I made contributions to a couple of open source projects, one of which was a design system being developed by the same organization. I used that to define HTML patterns and CSS. I used the framework I was just learning to start the application work. Put a database schema together and had a working prototype in two weeks that could also demonstrate the flexibility of front-end development. + +We set a meeting with the other Product Owners. I made more progress on the system and practiced the demo. We demoed it. The program turned upside a bit and we started making real progress. + +## 2016–2020 + +I remember working alongside one of the developers on the aforementioned program. We would have philosophical debates about web development. Eventually he said he had come to appreciate and understand the benefits of progressive enhancement. (I'm not sure how much [this article](/web-development/on-constraints/internet-bandwidth/) had to do with that decision.) + +The idea of the internet innovation curve came on stronger during this time and we would discuss ways to push what was possible, including pushing for progress in browser support for various features. + +## 2021– + +There seems to be a renaissance of the server-side application. The monolithic application even. More and more businesses and developers seem to be appreciating that not all websites need to leverage APIs and extensive JS. + +Meanwhile some organizations and businesses are just starting their microservice and single-page client-side web app journey. + +Which of these is on the bleeding edge and which is behind the curve? + +Or, maybe, they're both fine. The question I have is: Are you *choosing* the approach that best suits your needs, context, and constraints? If your company went all [.GM](General Motors) tomorrow, could you maintain your website? + +Let us also consider the notion of low- and no-code solutions, which were also available and promised in 2001. "Develop a full blown website without learning how to write code!" + +What does "modern" mean again? + +The codebase for this site as of this writing, would be considered a monolith. With that said, this monolith can deliver this site using: + +- dynamic page building and +- a static site. + +Would we be better served if this was *not* a monolithic app? diff --git a/content/public/web-development/on-constraints/content.md b/content/public/web-development/on-constraints/content.md new file mode 100644 index 00000000..1fa30559 --- /dev/null +++ b/content/public/web-development/on-constraints/content.md @@ -0,0 +1,19 @@ +--- +title: On constraints +dateblock: + - 20211101 Created on +--- + +# On constraints + +{!!dateblock!!} + +This series was started on Medium. Some of the articles in that series are more about [software development](/software-development) in general and is why this series will represent those topics specific to web development, at least in my mind. + +Granted, with how much we are depending on web-based [.APIs](Application Program Interfaces) these days, it seems almost all software development involves web development. + +Anyway, the idea of constraints is liberating to some and a complete non-starter for others. For the former, constraints help overcome the blank canvas problem. For the latter, constraints are hinderances to the creative process and "discovering" the constraints and applying them ourselves. + +## The series + +- [Internet bandwidth](/web-development/on-constraints/internet-bandwidth) diff --git a/content/public/web-development/on-constraints/internet-bandwidth/content.md b/content/public/web-development/on-constraints/internet-bandwidth/content.md new file mode 100644 index 00000000..496543b1 --- /dev/null +++ b/content/public/web-development/on-constraints/internet-bandwidth/content.md @@ -0,0 +1,734 @@ +--- +title: Internet bandwidth +dateblock: + - 20171204 Created on + - 20211101 Updated on +original: https://medium.com/8fold-pub/on-constraints-internet-bandwidth-eab05c20e218 Medium +--- + +# Internet bandwidth + +{!!dateblock!!} + +{!!original!!} + +All figures are "rough". And this one's a little longer and feels more rant-like than the others. Definitely full of snark. + +Two things have arguably contributed the most to what we know today (2017) as the inter-webs: + +1. the pornography industry and +2. limitations in download speeds. + +We'll focus on the latter. The web is built using three interpreted (uncompiled and uncompressed) languages: + +- [.HTML](Hypertext Markup Language), +- [.CSS](Cascading Style Sheets), and +- JavaScript (JS) + +The center of gravity is HTML. It's used as a view model for a browser to interpret and present a [.UI](user interface). It used to be pretty limited in what it could do. +So, what tends to happen is we use the "fringes" to overcome the limitations in the HTML-browser duo. Then, the fringe solutions get refined and pulled closer to the center, and become "native" to the HTML-browser experience. + +For example, for a time, JS was the only way to do animations on the Internet without some other technology (Flash, GIFs, and so on). Unfortunately, JS animations aren't hardware accelerated—they're literally a timer updating an HTML attribute over a span of time (HTML is the center of the universe); therefore, JS animations can get choppy depending on complexity and hardware restrictions for the user. + +So, we pulled animations and transitions into CSS. (But people still create animations using JS.) + +Client-side form validation could only be done in JS. Now, we've added attributes to HTML elements to the point that the browser can do it for us; no-JS. + +Moving on! + +Just like the 80 column constraint and all the others, the bandwidth constraint was once strictly imposed on us by external forces. Now, we have to decide whether it's a constraint we want to intentionally impose on ourselves. And, maybe rephrase the constraint to something like: + +> Stay as small as possible, as long as possible, then break out of the boundaries. When the new scale becomes comfortable, add more constraints to pursue technical excellence before adding more stuff. + +There are two main practices I use to help me here: + +- [Progressive enhancement](https://www.w3.org/wiki/Graceful_degradation_versus_progressive_enhancement). +- Test-driven-development (TDD) (that's a misnomer, I just mean incrementally automating manual tests, when they become too annoying or the code gets harder to read in one sitting). + +Progressive enhancement basically says, "I have no idea what device my user has. I have no idea what connection speed my user has. Therefore, I need to optimize for the unknown, while leveraging the known." + +You know what your server can do. + +Industrial-grade hardware, software, and connection to the vast reaches of the Internet. You don't know if your user is the billionaire Richard Branson using a super computer or a 10 year old Nigerian girl surfing the Internet on a Nokia phone to help finish her homework by street light. No matter if 80% of the population are Richard Branson, if you develop your system to cater to the 10 year old, the Richard Branson's of the world are covered as well. + +As the avant-garde ruling and steering the web, developers can easily get in the mindset that what everyone needs in order to survive, what they have access to, and what they want to play with are the same things developers do…this is not the case - at all. + +TDD basically says, break your functional requirements down into smaller technical requirements. Prioritize the technical requirements by any means you deem suitable (self-organization); I recommend finish-to-start and shortest time to complete, but whatever floats your boat. Only write the production code after you have written the test. Then, only write a new test when you receive a new requirement in your technical requirements backlog. + +To the backstory! + +*** + +A gentleman I know is learning how to be a software developer. He posted code the other day for a game he's building. It's a type of counter game. +From the code he posted, I pulled out these two functional requirements (there's a finish to the game, but this article will be rather long with a lot of examples already): + +1. In the game there are four items a user can click on. +2. Every time a user clicks on one of the four items, there is a score printed on the screen. + +There were some things I would have done differently to overcome certain constraints, but I'm not the "Master" to his "Padawan" of the [Jedi Order](https://en.wikipedia.org/wiki/Jedi) and he didn't ask for a code review; so, I didn't say anything. + +That is until someone else chimed in and said, "You should make that if statement a switch." + +This is one of those old-school debates in software development. Switches versus conditionals and when to use them. I don't like getting into these types of debates unless the difference can be empirically demonstrated at an order of magnitude better than the other. (They aren't different in purpose.) + +I did chime in on it though, because there was a deeper issue—the conditions could be deleted without side-effect or changing any other code. + +I said, "Here's a challenge for you. There are a few ways to do it given the technology stack you are using: remove the conditional entirely and allow for an infinite number of items. You can also remove your dependency on JQuery. Who knows, maybe this game will become a code kata for you to learn other languages; your own, personal FizzBuzz." +Then I realized, this is the perfect example to demonstrate this constraint. +Let's begin! + +*** + +Note: I've never built something like this. I've never seen these requirements. What follows is not me saying "look how awesome I am" (I actually think I'm a pretty poor developer all things considered); instead, it's demonstrating the importance of properly phrasing and applying constraints. (Now, I wrote the constraint; so, I know the intent…tell me how you could break the intent without breaking the rule, and I'll update the phrasing.) + +All right, I can't have four items, until I have one. I'm doing this on the web; so, I have HTML, CSS, and JS. I need the object to update a value somewhere that can be viewed somehow. And, most importantly: I need to stay as small as possible for as long as possible. + +Here you go (basic [VueJS](https://vuejs.org/v2/guide/components.html) without VueJS, [Angular](https://angular.io/guide/component-overview) components without Angular, [React](https://reactjs.org/docs/react-component.html) components without React, and so on): + +

+ + +
+ HTML (iteration 1) +
<button
+  style="
+    padding: 20px;
+    border: none;
+    border-radius: 5px;
+    background-color: #50e5db;
+	cursor: pointer;"
+  onclick="
+    var targetValue = parseInt(this.innerHTML) + 1;
+    this.innerHTML = `${targetValue}`;">
+0
+</button>
+
+
+
+ +Everything is in the button element, sticking to our constraint. And it's fully testable without needing anything other than the ability to "click" and the ability to retrieve the value attribute. + +And, about a decade or so ago, this would have been the only way to do it. But there's a problem…I need four of them…no problem, just copy and paste (and back-in-the-day you would have to): + +
+ + + + + +
+ HTML (iteration 2) +
<button
+  style="
+    padding: 20px;
+    border: none;
+    border-radius: 5px;
+    background-color: #50e5db;
+	cursor: pointer;
+  onclick="
+    var targetValue = parseInt(this.innerHTML) + 1;
+    this.innerHTML = `${targetValue}`;">
+0
+</button>
+<button
+  style="
+    padding: 20px;
+    border: none;
+    border-radius: 5px;
+    background-color: #50e5db"
+  onclick="
+    var targetValue = parseInt(this.innerHTML) + 1;
+    this.innerHTML = `${targetValue}`;">
+0
+</button>
+<button
+  style="
+    padding: 20px;
+    border: none;
+    border-radius: 5px;
+    background-color: #50e5db"
+  onclick="
+    var targetValue = parseInt(this.innerHTML) + 1;
+    this.innerHTML = `${targetValue}`;">
+0
+</button>
+<button
+  style="
+    padding: 20px;
+    border: none;
+    border-radius: 5px;
+    background-color: #50e5db"
+  onclick="
+    var targetValue = parseInt(this.innerHTML) + 1;
+    this.innerHTML = `${targetValue}`;">
+0
+</button>
+
+
+
+ +Two problems. One, we're repeating ourselves; thereby, breaking the DRY principle, which unabashedly and probably with full knowledge of its own irony, states: don't repeat yourself. + +For procedural code, which causes side-effects you want to avoid duplication because it means if you change the logic or behavior for some reason, you need to remember (or find) all the places the old logic exists; so, another constraint is related to how you should feel when you are about to copy and paste code - from anywhere. + +But, this article focuses on bandwidth. + +Every letter you write is roughly 8 bytes in UTF-8, which is the predominate font standard on the web today (we used to use another that took less space per character, but it had its own limitations—to use an apostrophe or not to use an apostrophe, that was the question); this 8 bytes does include white space characters (every key stroke, costs you 8 bytes, just remember that). + +We have 28 characters in our code right now; so, we're at about 224 bytes. Back in the day, the average (because it was the only) speed you could go was 14kbs (there were slower modems, but this speed was the one that really penetrated the market for purposes other than fax machines). Right now, this single page client-side web-app would load instantly at that speed. + +Having said that, back in the day we wanted to do a lot more than put 4 buttons on a page for people to count things with; therefore, we had to figure out ways to reduce duplication because "broadband" for consumers was roughly 1mb in speed and ran about $100+ a month (for half that cost, I get 10 times that bandwidth 20 years later); therefore, nobody except industry was paying for broadband (they're paying for faster, more expensive stuff now). + +Eventually browsers started caching remote assets downloaded in separate requests compared to the HTML file; so, when you went to a site the first time to look at that ::cough:: completely innocent image of kittens ::cough:: you had to wait a few seconds but, if you went back within a certain period of time, no worries, because the image was already on your device (then all the Flash folks had to figure out workarounds for caching so we could update our sites and have users see them). + +So, we cache the image. We also may cache the CSS and JS; possibly even the HTML, all on your device. + +We don't have a functional requirement for multiple pages, much less having multiple pages with similar functionality; so, we don't have a technical requirement to store the CSS and JS in separate files: separate HTML, CSS, and JS files. We should leave flexibility in the system to be able to do that but, in keeping with staying as small as possible for as long as possible, we shouldn't get ahead of ourselves; therefore, we'll put the CSS and JS in the `head` of the document. + +Sometimes downloading one large file can be faster than downloading a bunch of smaller files. It's not typically faster when we measure "time to first paint"; the time from request to seeing something on the screen, however, increasing the number of network requests increases the number of delays compared to an established connection and stream. Those delays from making individual calls can be slower than streaming the larger file on a slower connection. (I'm looking at all of use who have made multiple [.API](application program interface) calls server-side to then deliver a larger aggregated chunk of data to the client.) + +Anyway, separation of concerns. + +Get your CSS and JS out of your HTML! Was the rally cry in 2007. + +And so we did. + +
+ + + + + + + +
+ HTML (iteration 3) +
<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+
+
+ +
+CSS (iteration 3) +
button {
+  padding: 20px;
+  border: none;
+  border-radius: 5px;
+  background-color: #50e5db;
+}
+
+
+ +
+JavaScript (iteration 3) +
function increment(button) {
+  const targetValue = parseInt(button.innerHTML) + 1;
+  button.innerHTML = targetValue;
+}
+
+
+
+ +And this, I fear is how things start getting complicated to keep track of. + +We still have four instances of one object—the button—the HTML tells us so. Each object definition spans three files though. It's kinda like object inheritance or traits in [.PHP](PHP: Hypertext preprocessor). +Having said that, this is the one-ish line solution for my friend; no conditionals. (It also demonstrates why he doesn't need them…this is not a method called outside of the button, it is something the button has. When we do the `forEach` in the anti-pattern section later, we'll lose the obviousness of this relationship.) + +We have that first requirement pretty well satisfied and back in the day, this page would have taken a little over a second to download. We also added the "feature" of each button displaying its value (really we did that so it was obvious for us to see and manually test). Not bad. + +Still independently testable (though we might not need to write automated tests for a roughly 10 line piece of code, if something breaks, we can probably find it). Still as tight as we can make. + +One other requirement to satisfy. We need to display the total score somewhere on the screen. Let's use a paragraph and have the buttons update the scoreboard for us, no need for a "score keeper" object or anything, we aren't feeling any pain from not having one of those (for the purposes of this example we're not creating live areas for accessibility). + +
+ + + + +

0

+ + + +
+ HTML (iteration 4) +
<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+<button onclick="increment(this)">0</button>
+<p id="scoreboard">0</p>
+
+
+ +
+CSS (iteration 4) +
button {
+  padding: 20px;
+  border: none;
+  border-radius: 5px;
+  background-color: #50e5db;
+}
+p {
+  font-size: 1.5rem;
+  font-weight: bold;
+  margin: 2rem;
+}
+
+
+ +
+JavaScript (iteration 4) +
function increment(button) {
+  const targetValue = parseInt(button.innerHTML) + 1;
+  const scoreboard  = document.getElementById('scoreboard-4');
+  const scoreboardCurrentValue = parseInt(scoreboard.innerHTML);
+  button.value = targetValue;
+  button.innerHTML     = `${targetValue}`;
+  scoreboard.innerHTML = scoreboardCurrentValue + 1;
+}
+
+
+
+ +We could make the code smaller, by removing the whitespace and other letters: + +``` +function increment(b) { + const t=parseInt(b.value)+1; +} +``` + +And we used to do that, but we have compilers that'll do that for us; so, many of us aren't as concerned about being terse. + +Of course, that adds a build phase we have to run before releasing things, a package dependency, knowledge about packages in general, and a deployment and integration phase…but maybe the gain is worth the cost when we consider the constraints (you and I aren't feeling the pain of not having a build phase, and neither are our users). Further, browsers and HTML keep absorbing things that used to only be available at the fringes; so, who knows, at some point, we may be able to chuck this and replace it with the new stuff…and, right now, it wouldn't hurt a bit to do so. + +Note: All examples use plain JS; no libraries. (That's why everything is in the global scope instead of encapsulated somehow; the only thing to collide with from a naming perspective is JS itself.) In fact, there is no JS library available I'm aware of smaller than the one we built specific to our needs. (That's why my friend can also get rid of JQuery; instead of forming the bad habit of automatically including it in every project from here until eternity.) + +I'm not hating on JS specifically, I'm simply making one more argument as to why the progressive enhancement approach (catering to the little girl in Nigeria and gravitating to the core of the web) is still of great benefit to web development and the overall user experience and how strict TDD sort of supports this notion. + +The other thing is that there's a fourth level of fringe…libraries and frameworks. + +Most library and framework developers do not actively try to deprecate themselves, for completely understandable reasons; therefore, it is up to us to use them to spin up quickly, and actively decouple from them (Kevlin Henney once pointed out that 95% of UNIX is portable, written in C, you only have to write the other 5% to make it work on different platforms). + +(I actually respect the [Bourbon](https://www.bourbon.io) team a lot for this, removing functions from their library as CSS gains the ability natively.) + +*** + +## Anti-pattern creep + +(This is the area of the half-kidding jokes, and heaviest snark, I think. Starting now in fact.) + +It's important to note, everything up to this point was to satisfy business and our users. Everything after this point, as developers, is to satisfy ourselves. Having said that, since business and our users didn't explicitly ask for it, they're not here; therefore, it is masturbatory and has no business being submitted with our final solution. + +Ha! The Internet really [is for porn](https://www.youtube.com/watch?v=LTJvdGcb7Fs) (YouTube, Avenue Q). + +This next bit is developer porn. (There's designer porn too.) + +For PHP, the mission was to get any `` tags out of HTML (despite the language being created specifically to put PHP into your HTML, I was actually a champion of removing PHP tokens myself for about a month); that led to the creation of all sorts of template engines that gave the appearance of getting rid of PHP in HTML, but not really. (Template engines made out of a template engine.) + +For CSS it was abandoning the use of the style attribute in your element at all costs; to be replaced by 1 class attribute name to "customize" a single element on a page, which has itself become bloated as we now glue class names together to allow "fine-grain" control and avoid repeating our individual CSS definitions. "I need one, and only one, display: block in my CSS!" Granted the class name is .display-block; so, we have literally saved nothing. We don't like going to separate files, I get it, that's why staying as small as possible, as long is possible (and actively trying to consume what you have written previous) is more important than adding more stuff. + +For JS the argument was made that calling JS methods inside an HTML element was not just taking advantage of event handling afforded by the platform and browsers, it was still JS inside HTML and had to go. + +So, we end up with something like this: + +
+ + + + +

0

+ + +
+ HTML (iteration 5) +
<button>0</button>
+<button>0</button>
+<button>0</button>
+<button>0</button>
+<p id="scoreboard">0</p>
+
+
+ +
+CSS (iteration 5) +
button {
+  padding: 20px;
+  border: none;
+  border-radius: 5px;
+  background-color: #50e5db;
+  cursor: pointer;
+}
+p {
+  font-size: 1.5rem;
+  font-weight: bold;
+  margin: 2rem;
+}
+
+
+ +
+JavaScript (iteration 5) +
var buttons = document
+  .getElementsByTagName('button');
+Array.from(buttons).forEach(function (button) {
+  button.addEventListener('click', function() {
+    const targetValue = parseInt(button.innerHTML) + 1;
+    const scoreboard = document.getElementById('scoreboard-5');
+    const scoreboardCurrentValue = parseInt(scoreboard.innerHTML);
+    button.value = targetValue;
+    button.innerHTML = `${targetValue}`;
+    scoreboard.innerHTML = scoreboardCurrentValue + 1;
+  });
+});
+
+
+ +``` +705 characters * 8 bytes = ~6kb +``` + +(This is what I mentioned before about not being obvious as to why you don't need the conditionals, and don't need to do anything special to delete them. We are adding the listener to a single button instance…not a collection of them; therefore, I don't need to know or find out which button it was, the button is the one who made the call in the first place, not the document, and I have access to the button directly because of that. Again, if we start learning web development at the fringes and view HTML as something to be acted upon, not the actor itself, this being a function call from a single button is not obvious and I need to know how and what the JS code is actually doing in that loop to not overcomplicate things.) + +Still, not too bad. Still under one second on a 14.4[.kbps](kilobytes per second) connection. But, the character count in the JS is starting to creep and we're not adding any new functionality or gaining any performance. But, this is the only thing I'm developing today; so, you know, I'll keep working on it anyway. + +The value attribute, after all, is only there to set the initial value of the application. It's only reason to exist is to inform JavaScript of something. And we must remove all JS-related code from the HTML. + +Fine, no JS-related stuff in the HTML. + + +> `Same as previous - removed use of value attribute in the button.` + + +Brings us to about 100 characters for the HTML ([.1kb](one kilobyte)). About 500 for the JS ([.4kb](four kilobytes)). 663 all together or [.5kb](five kilobytes) still. So, time to first paint on a 14.4[.kbps](kilobytes per second) modem is ~0.1 seconds. Time to first interaction is ~0.3 seconds. + +We've made it almost three times larger than iteration 4, without adding any new functionality. But, at least we don't have anything related to JS in the HTML. (It's important and difficult to properly phrase and apply constraints.) + +You want smaller HTML? I'll show you smaller HTML. We also did say, don't repeat yourself, and those four HTML buttons are just repeats, not equivalents to instantiating an object. (Enter JS—and other—template engines). + +
+
+

0

+ + +
+ HTML (iteration 6) +
<div id="cont"></div>
+<p id="scoreboard-6">0</p>
+
+
+ +
+CSS (iteration 6) +
button {
+  padding: 20px;
+  border: none;
+  border-radius: 5px;
+  background-color: #50e5db;
+  cursor: pointer;
+}
+p {
+  font-size: 1.5rem;
+  font-weight: bold;
+  margin: 2rem;
+}
+
+
+ +
+JavaScript (iteration 6) +
var container = document.getElementById('cont');
+for (var i = 0; i < 4; i++) {
+  let button = document.createElement('button');
+  button.innerHTML = 0;
+  container.appendChild(button);
+}
+var buttons = document.getElementsByTagName('button');
+Array.from(buttons).forEach(function (button) {
+  button.addEventListener('click', function() {
+    const targetValue = parseInt(button.innerHTML) + 1;
+    const scoreboard = document.getElementById('scoreboard-6');
+    const scoreboardCurrentValue = parseInt(scoreboard.innerHTML);
+    button.value = targetValue;
+    button.innerHTML = `${targetValue}`;
+    scoreboard.innerHTML = scoreboardCurrentValue + 1;
+  });
+});
+
+
+ + +About 200 more characters (2 more kilobytes, for a total of [.7kb](seven kilobytes)) and no new functionality. + +But, boy that HTML is tiny. And all, and I do mean all, of the JS-related code is out of the HTML. + +In fact, the HTML is now dependent on JS to display the initial state of the app. (What we used to get with one plain text file…including the interaction definitions; go us!) + +Therefore, time to first paint is now equal to time to first interaction. + +"But wait! My front-end developer doesn't know how to do JS!" + +"That's okay, we'll make it so all they need to do is add this obscure attribute to this partial that looks like HTML, so our JS can read it (thereby attempting to either replace or duplicate the browser itself, because we get that for free in the browser). Or, we'll interpret your request as the market demanding more JS developers, and create courses online to fill that void. Or, both!" + +(Those last two might be below the belt, but this is a topic I'm pretty passionate about and opinionated on; JS may replace HTML at some point, but that is fringe of fringe of fringe compared to present reality and the problems most people need to honestly solve to survive…it's the web development equivalent of cryptocurrencies.) + +"Wait…isn't that whole partial-plus-attribute thing what we were doing initially, only without the obscure part? Isn't that putting JS in the HTML?" + +"No, not at all! Because calling my framework from an HTML partial is totally different than calling JS directly without my framework." + +"But now, my front-end developer who only cares about HTML and CSS has to learn your framework?" + +"Absolutely! But that's okay, my framework is awesome." + +…and so it continues. + +But that's okay! Because we'll come up with some other technology workaround to a problem most people don't actually need to solve to satisfy the requirements we solved four or five iterations ago because JS is the new HTML and we need more of it, not less (I'm quoting that one, not hating—we need more JS was actually quoted to me once, probably out of context from the original, during a debate over whether to [.SPA](single-page client-side web app) or not to SPA). + +But, you know what makes the 8th solution really awe-inspiring? (Emphasis on the sarcasm.) + +When that darn product owner says, "I want five buttons instead of four," we just have to change the 4 in that loop to a 5. + +(Granted, we also need to know what a for each loop is…what the 4 is doing…which means we should probably add comments…we need to know that the four exists, and in which file…as opposed to going to the HTML, seeing 4 buttons, and adding another one. The web, at one time was the most inclusive and accessible platform that allowed anyone anywhere to create really cool things…and we seem to be actively trying to make it appear more and more like magic, for whatever reasons. It's plain text, defining the look, feel, and interaction capabilities of an object that a browser then interprets and renders on the screen…the equivalent of a plain text email makes what you see on the Internet.) + +And, of course if, for whatever reason, I need to change from using all buttons to a hodgepodge of `button`, `img`, and `div` I sorta have to start over; or, come up with another, more convoluted solution to a problem I didn't really have in the first place; good thing it's so small. + +(Wait, who am I kidding, we already use `div` for everything. We should have just started there…and a non-trivial number of us have—`div` with `class` of paragraph. But because these are inline, we would use `span`, because that whole [semantic web](https://www.w3.org/standards/semanticweb/) thing is totally last decade. The spec literally says, [avoid these two elements](https://html.spec.whatwg.org/#the-div-element): Authors are strongly encouraged to view the `div` element as an element of last resort, for when no other element is suitable. Use of more appropriate elements instead of the `div` element leads to better accessibility for readers and easier maintainability for authors.) + +This is one reason it's sometimes faster to throw out 1,000,000 lines of code and start over than it is to iteratively fix what you have…but business must press on; so, we'll set up a parallel team and product…while still maintaining the original (legacy)…we won't release the "alt product" until…until the beta product has been overtaken by the market…a risk we'll mitigate by…setting up a parallel team…repeat. + +[Stop it, get some help.](https://youtu.be/l60MnDJklnM) (YouTube) + +Oh! And see how the CSS is sitting over there with that, "Please don't hurt me" expression? Don't worry about that, we've overcomplicated that too, and made the complex solution the standard solution (calling it a best practice, of course). First thing I should have done in fact is grab JQuery and Bootstrap…I'll need them *eventually*…and that seems to be what everyone else is doing. + +We've even added compilers and a build phase to CSS. Granted CSS is a little closer to the center; so, it doesn't get beat up on and toyed with as much as JS (yet), being that JS is at the fringes and usually where developers start learning web development these days. We also don't really beat each other up over CSS, unlike which JS frameworks you prefer. Designer-types tend to view technology in a different way than developers…of course, at the rate we're going, designers are gaining more and more reasons not to learn CSS. And they already don't want to learn HTML (because no one really seems to want to learn HTML), which is unfortunate, because it's dead simple to do some amazing things. (It can be painful to write though, I will admit.) + +It sometimes literally feels like we (web developers) say to ourselves, the Internet is going too fast and is way too simple; what obscure layers of technology and architecture can we put in place to slow it, the users, and our own development of it down while simultaneously making it appear like voodoo requiring extensive training to do the most basic things? (We passed the time of the browser wars and have entered the period of the framework wars…again…long story, and meanwhile I'm over here like, "You know we have that already, right?" or, "You know that's part of the vanilla HTML, CSS, and JS ecosystem now, right?") + +There are a lot of factors at play here as to why we keep building complex solutions to simple problems. I think one of the biggest can be lessened by implementing value-based billing. But, that's a different article; here's a hint: + +It took me about 3 hours to create this—not because I'm awesome (that might [actually be too long](https://youtu.be/xGmXxpIj6vs)), remember the note in the beginning, I consider myself a pretty poor developer in the grand scheme. That 3 hours includes the article. All the fiddles. And trying to figure out Medium's embed caching situation. Breaking down the functional requirements, having never made something like this before. (I also don't really know JS well enough to do it from memory because, like the 80th column, I rarely actually hit that point and haven't since about 2010; so, I had to search for how to do most of that part.) If someone valued all of this at $200 and was willing to pay for it, I just got paid roughly $60 an hour. (Not focusing on value-based billing and work is part of how I put myself out of business in 2010 and became homeless.) + +The code itself probably took 20 minutes; with all the iterations. Factor in unit tests I didn't write, probably about an hour to get here ($200 an hour)…the 8th iteration of which is way farther in complexity than it needs to be for the actual requirements, but nowhere near as [complex and complicated as it could be](https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition). Thereby, leaving me 7 hours, to make it "look pretty" if I wanted and worked an 8 hour day for that $200 (idle hands are the Devil's play thing). + +But, "I'm not a designer" is the retort I hear most… + +"Then you have 7 hours to practice and become more cross-functional and learn a new skill…become more 'full-stack'," is the counter I will offer, with sincere love and respect for all that you are; after-all I still say I'm not a "developer" because I really like "designing" things; so, I make development go faster to spend more time thinking and designing new things instead of continuing to complicate what's already there. + +Thanks for letting me get that off my chest. I really do appreciate it. + +*** + +Seriously though. We were done with the requirements at the 4th iteration. + +- 244 characters of HTML: 2k +- 100 character of CSS: 1k +- 300 characters of JS: 2k +- Roughly 0.3 seconds at 14kbs + +So small that the sky's almost the limit on what we could add visually. + +It was all pretty readable and reasonable (could be reasoned about). Not only that, and here's the interesting part: I didn't have to guess whether or not an HTML element could be acted upon or in what ways. + +Bears repeating and overemphasizing. + +> I did not need to guess, from looking at the HTML element, whether it could be acted upon or in what ways. + +Now we're at ~7kb with no new features for the users. No added benefit for our Nigerian friend (we've actually made her experience slower). And we have an application with 4 buttons (that appear by "magic" to the layman). And the buttons turn a counter…seriously? + +I could have made this solution way more complicated though, trust me—just look at [left-pad](https://github.com/left-pad/left-pad), it adds a character to the beginning of a string, is broken in two use cases, and is now JS-native functionality. The original author tried to take it down and broke the Internet. The package is marked as deprecated and the repository is archived. Left pad still gets almost 3 million downloads a week. + +What are we doing, web developers…seriously? + +To top it all off, Bob Martin, Pragmatic Dave Thomas, or Kevlin Henney will show up tomorrow and manage to do what I did with half the code, in half the time, and without having to know the requirements. + +But, sticking to our constraint of: + +> Stay as small as possible, for as long as possible, then break out of the boundaries. When you become comfortable at the new "scale," add more constraints before adding more stuff. + +I literally could not unintentionally make this more complex and complicated. + +I would have had to start with trying to build the fifth iteration first (though I did have an idea or vision of it, I just didn't force it). Beginning with the end in mind, is not the same as trying to begin at the end. + +What could have been the "final" solution (iteration 4), didn't have to use JS to parse the HTML ([.DOM](document object model)) to find the buttons (could leave the JS in the head of the document because it was purely functional and the button managed its own state). Didn't have to wait, or verify, the DOM had finished loading. + +Don't get me wrong, there are times where it can get a little messy in the HTML if you keep putting everything inside the individual elements; thereby, treating HTML like a first class citizen in the Internet ecosystem instead of some arcane, legacy troll in the basement to be acted upon by other things…but that's the point of the constraint. + +Stay as small as possible. When it becomes too painful to reason about. Make it smaller, by moving things somewhere else. + +"But I need to scale!" + +"How many external users do you have right now?" + +"None." + +"Right. How hard is it to send 3kb over an internet connection? How many servers will you need to handle the 5,000 users you don't have?" + +Anyway… + +Back to reasons HTML is still around…ummmmm…oh…right…there wouldn't be an internet UI without it. + +There also wouldn't be the Apple UI, it uses [.XML](eXtensible Markup Language) too. There wouldn't be the Android UI. There wouldn't be SVG. There wouldn't be NativeScript. There wouldn't be a lot of stuff when it comes to user interfaces without HTML and all of its XML-based cousins. What we don't like is writing it. That's why no one celebrates or knows most UIs are XML-based; we've put layers of abstraction and technology over it to try and hide it so people won’t have to write it (MS Frontpage 2000, baby, that's what I used back in the day). + +Unfortunately, tools that write it for you still kinda suck, almost 20 years later. Therefore, if you care about craftsmanship, you're still writing most of it by hand; or, your product has already incrementally scaled over the course of a decade, I'm looking at you Facebook. + +> You get it? Ogres have layers! + +HTML is the deepest layer when it comes to UI development for the web, and its parent XML, is the deepest layer for just about every UI you interact with. + +To beat the horse dead (too late), remember the constraint: + +> Stay as small as possible, as long as possible, then break out of the boundaries. When the new scale becomes comfortable, add more constraints to pursue technical excellence before adding more stuff. + +If you have a large codebase and think it could or should be smaller, time to start piling on the constraints. Further, start thinking progressive enhancement and ask yourself if you only had HTML, what would/could you accomplish (you might be surprised)? + +(Cater to the Nigerian girl doing her homework on a Nokia phone at 3kb per second by street light - Richard Branson can take care of himself because, given his humanitarianism, he will most likely applaud your site's ability to load instantly for him while almost doing the same for her). + +Finally, practice your TDD kata on that codebase, see what happens, you might be surprised. diff --git a/content/public/web-development/refactoring-re-engineering-and-rebuilding/content.md b/content/public/web-development/refactoring-re-engineering-and-rebuilding/content.md new file mode 100644 index 00000000..b0b30279 --- /dev/null +++ b/content/public/web-development/refactoring-re-engineering-and-rebuilding/content.md @@ -0,0 +1,83 @@ +--- +title: Refactoring, re-engineering, and rebuilding +dateblock: + - 20211102 Created on +--- + +# Refactoring, re-engineering, and rebuilding + +Someone mentioned the word "refactoring" the other day. Someone else said, "Let's call it rebuilding." + +This got me thinking. + +Language is interesting. I hold that the words we use to describe things can often fail to grasp the idea in someone's mind. We are unlimited in our capacity to think and dream; however, we are limited in our vocabulary to describe—no one knows *all* the words. + +I had an unabridged, English dictionary once. On the cover it said something like: Over 300,000 words. I took a test once that said my vocabulary was somewhere in the neighborhood of 3,000 words; less than 1 percent of the words in the dictionary. + +Add to this the tendency many of us have to be loose with words and we end up in an interesting position. Not only will I most likely choose the wrong word sometimes but your interpretation, denotation, and connotation of that word may not be what I intended. Of course, intent and impact are not mutually inclusive. + +Anyway. + +We're going to start this one with some definitions and a reference to one of what I'll call The Three Laws. + +We'll start with the easier one: Rebuilding. + +Rebuilding is the act of building something again after it has been destroyed. This is contrasted with creation, which is building something for the first time. Rebuilding is an act of creation; however, the intent is to bring back what was once there, mostly. + +In the context of the aforementioned conversation, we were talking about software development; specifically, a system that was being built to replicate and replace the system already in place. One person said the team had refactored the system. One of the developers on the team disagreed with that assessment. + +So, refactoring. [Refactoring](https://refactoring.com) as defined in the book of the same name states something to the effect: Refactoring is changing the internal structure of an existing system in such a way that we do NOT alter its external behavior. + +Contrast that with re-engineering, which I define similarly as: Re-engineering is changing the internal structure of an existing system in such a way that we DO alter its external behavior. + +That means I could completely rewrite the operating system for the device you are reading this on and, as long as I leave certain things alone, all I've done is refactor that code. Further, if you're walking down the street and something happens that causes you to interact with the outside world in a different way, you have just re-engineered your brain. + +Now for the law. It's referred to as [Conway's Law](https://en.wikipedia.org/wiki/Conway%27s_law) and it states: + +> Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure. +> +> — [Melvin E. Conway](https://twitter.com/conways_law) + +Conway's Law has been reframed and rephrased more than a few times. Mine goes like this: + +> Conway's Law swings both ways. Show me the people and I'l describe the code. Show me the code and I'll describe the people. + +Of course, it doesn't have to be code, system being defined broadly in this context. With that said, it often works out. Take the [code used to build this site](https://github.com/8fold/site-joshbruce.com). + +As of this writing, there's a minimum number of dependencies. There's a minimum amount of code. And, it's relatively easy to deploy and release new versions of code and content; I can do almost everything that needs doing using my phone. + +So, what does this say about me? + +When it comes to [motivators](/design-your-life/motivators), autonomy is ranked number two almost every time; I don't like depending too much on others and I try to help other not be dependent on me. I'm a minimalist in my lifestyle when it comes to people and property. I also tend to refactor what I say to deliver the most impact possible with the fewest words I can consider [at the time](https://quoteinvestigator.com/2012/04/28/shorter-letter/); I try to be to the point. I favor mobility and agility in my life. + +I describe the code and the code describes me. + +If you work in an organization with a lot of rules and governance, chances are your code will have a lot of rules regarding style, submission, and so on. But here's the rub, if you develop your code with a lot of rules, chances are you will have an expectation of a lot of rules within the organization. Teams who are more rules-oriented and focused will often find it difficult to tolerate things that are more loose. + +## Rebuilding + +I [rebuilt this site](https://joshbruce.com/web-development/2021-site-in-depth) with these things in mind, in fact; the only things I kept were the [.CSS](Cascading Style Sheet) and the libraries I use for building HTML ([HTML Builder](https://github.com/8fold/php-html-builder) and the [CommonMark extension](https://github.com/8fold/commonmark-fluent-markdown)). I maintain the code for both of those packages (dependent on myself). + +And, in under 48 hours, I redesigned the site, removed my dependency on a framework, and improved my workflows. + +## Re-engineering [.vs](versus) refactoring + +I recently refactoring the code for the site. Specifically, I performed the [extract class](https://refactoring.com/catalog/extractClass.html) refactoring to create a class called [`FrontMatter`](https://github.com/8fold/site-joshbruce.com/pull/19). From the perspective of a user of the site, nothing changed, it's a refactoring after all. + +However, from the perspective of the other classes within the system, this was re-engineering. The way the other classes interacted with the other pieces of the system changed pretty dramatically. To put it in corporate human terms, I hired someone new to take on the responsibilities that were once owned by another person. This meant everyone who used to interact with that original person now had to interact with this new person. + +As of this writing, if we think of files as an individual, there are 15 team members. Introducing this one new team member forced me to change 14 files. This is an integrated team. Each member contributes something to the goal of delivering the proper response to you, the user of the site. + +This makes me think that maybe there's no such thing as refactoring. + +Is it really possible to put someone on a team (or alter code in some way) wherein the external behavior doesn't change at least a little bit? + +I talked about [staying as small as possible](/web-development/on-constraints/internet-bandwidth) for as long as possible and I would say that applies to team size as well. I was in an interview once and was asked when I thought it was appropriate to scale. + +That's a loaded question. When we talk about scaling we usually only talk about it in terms of scaling up. More code. More people. And I think those two things are mutually inclusive. The more people independently contributing to a codebase, the more code there will be. We rarely talk about scaling down though. + +I didn't have a good answer for that question at the time. I'm not sure I have one now, in fact. I think it's appropriate to breathe. Scaling up is an inhale and scaling down is an exhale. I think it's appropriate to scale in either direction at any time; however, one should favor the exhale. + +But this isn't the article for that I don't think. + +So, what do you think? Is it possible to refactor a system without re-engineering it in some way? diff --git a/content/public/web-development/static-dynamic-and-interactive/content.md b/content/public/web-development/static-dynamic-and-interactive/content.md new file mode 100644 index 00000000..f8b380ca --- /dev/null +++ b/content/public/web-development/static-dynamic-and-interactive/content.md @@ -0,0 +1,107 @@ +--- +title: Static, dynamic, and interactive +--- + +I've seen multiple components to talking about site content; here's what that looks like for me at the moment. + +Static +: The [.HTML](HyperText Markup Language) used by the browser is pre-rendered. +: The HTML may, or may not, exist as separate files; using a form of a front-controller or other middle layer would still fall into this style so long as the pre-rendered HTML ins't altered. + +Dynamic +: The HTML for the site is generated, or manipulated, based on the specific request being made. + +Multi-page +: A request from a browser may resolve in interacting with different files per request. + +Single-page +: A request from a browser will always interact with the same file with each request. + +Server-side +: The HTML is rendered on the server, either as static files, dynamically generated strings, or a combination. + +Client-side +: The HTML is rendered or manipulated in the browser. + +Database-driven +: The content is stored in a database (hard drive, usually somewhere else) such as [MySQL](https://www.mysql.com) or similar. +: The relationships between content data are stored in a database. + +Flat-file based +: The content is stored on disk, usually in the form of plain-text. + +As of this writing, if you go to [joshbruce.com](https://joshbruce.com), you will be experiencing a single-page, server-side dynamic site that is flat-file based. + +It's running on an [Apache](https://httpd.apache.org) server on some version of [macOS](https://www.apple.com/macos/). That means, I can use a [`.htaccess`](https://github.com/8fold/site-joshbruce.com/blob/c5947f597f017983380d91a01b4cec834ef9b357/public/.htaccess) file to give instructions to the server without administering the server directly. The file basically tells the server, "No matter what [.URL](Uniform Resource Locator) the user requests, use this `index.php` file instead." + +That's what makes it a single-page application (or site). + +The file doesn't get returned to the browser. Instead, it runs the script line-for-line (procedurally) inside the `index.php` file, which will instantiate objects as necessary to achieve the goal of returning the appropriate response; a controller—sometimes more specifically referred to as a "front controller." All of this code runs on the server until it finishes or times out, whichever comes first. Once the scripts are done running, the server shuts down the application that was started specifically for that single request. + +Because the app is running entirely on the server, it's a server-side application (or site). + +As of this writing, there isn't a single line of HTML anywhere in the content or [code for this site](https://github.com/8fold/site-joshbruce.com). + +## Static sites + +The drawback felt by having a static was that it was so hard to change the structure of pages and look and feel of the site. + +Want to update the navigation? Sure thing, go through every page of the site and copy-and-paste the HTML you want updated. + +Two things came about to help overcome this (in my estimation): + +1. server-side scripting languages and +2. [.CSS](Cascading Style Sheets). + +Scripting languages like [.PHP](PHP: Hypertext Preprocessor) allowed you to create HTML snippets and templates that could be composed into a final, rendered page and sent back to the server. Effectively turning the site into a dynamically-generated site. CSS let you redesign the site [in multiple ways](http://www.csszengarden.com) without having to change the structure of the HTML. + +## Dynamic sites + +The drawback to dynamic sites has always been speed. + +It's a lot faster for a server to find a file and send it back to the person requesting that content. Granted this only seemed to become a problem when your site hit a certain level of users all vying for the attention of the server. + +The ability to server different experiences based on different parameters was awesome though. Designers would sometimes create different CSS to cover different times of year. Keeping track of sessions made it possible for users to sign-in and customize the site. The idea of users having the ability to customize their experience was a pretty big deal for a while. + +## Multi-page sites + +The drawback to multi-page sites was that it was difficult to keep the site architecture and code architecture straight; depending on the number of separate files. + +The benefit here though was the ability to mix-and-match experiences and server gets to be a server. I could have part of my site be static while another part was dynamic. Optimizing the experience for the content and user needs. + +I'm pretty sure this is where [joshbruce.com](https://joshbruce.com) is headed. That is, of course, so long as I don't get bored and it seems appropriate. + +## Single-page sites + +The drawback to the single-page site is that you're kinda all in on an approach and technology stack. You also take on more of the responsibilities usually left to a the server software of your choice. + +The benefit though is that it can be, theoretically, easier to keep track of. However, with the way things are going with frameworks and the like, I'm not so sure. + +Of course, if we're talking about a single-page, static site, life just became dead simple. Some businesses did just that around 2005 as the social media platforms were starting to take off. The site was one page with links to other places where you could interact with the owners and their content. + +## Server-side + +I've been exposed to two primary arguments against a server-side application or site: + +1. full page refreshes and +2. they're slow. + +The slow part usually doesn't happen until your traffic outperforms the hardware you're running on. Each network call has to go through two or three steps before even reaching the server (another computer somewhere connected to the Internet and running some type of server software). The first is resolving the domain name and the second is in the [.TCP](Transmission Control Protocol), which I'm not qualified to speak much on. No matter what, these two things need to happen before the server does anything. The more requests made, the longer it will take the site to load. Each image asset, each style sheet, and so on. Basically, if it's not text and is requesting from the server, the connection can become the bottleneck. Each request can take around 100 milliseconds to connect to the server. + +Then the speed is on you. + +How big are the assets? Do you compress the assets before they're sent back? How consolidated can you make other parts of the experience? + +## Client-side + +The big drawback for me here is that accessibility begins to become challenging and a lot more work. The entire site essentially becomes one big [`aria-live`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) region. And we lose what we had for free from the server-side. + +Another drawback is you don't know how cool the machine is running your site. + +Basically, we ask the user's hardware take on the role of a server. Further, we ask our code to do the same, as well as taking on the role of the browser in certain respects. If someone isn't running on good hardware, they could have a bad experience. The, "It works on my machine" problem. We don't know their connection speed; or, will need to account for it, which means more code. + +Another drawback is the notion that people can turn off client-side scripting languages; though this is unusual. + +Some of these drawbacks are only around if we're building a single-page experience. + +For example, having a component of the page the user can click on and perform some action, like adding an item to a shopping cart, doesn't take on the responsibilities of the browser and server wholesale. Instead, the script takes on the message sending and receiving capability of the browser and may take on the rendering responsibility of the server. This would fall into the interactive piece. diff --git a/setup-errors/500.md b/error-500.md similarity index 100% rename from setup-errors/500.md rename to error-500.md diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..258afa02 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,42 @@ +const gulp = require("gulp"); + +const autoprefixer = require("autoprefixer"); +const csso = require("postcss-csso"); +const discardComments = require("postcss-discard-comments"); +const postcss = require("gulp-postcss"); +const rename = require("gulp-rename"); +const sass = require("gulp-dart-scss"); +const sourcemaps = require("gulp-sourcemaps"); + +gulp.task("default", (done) => { + console.log('gulp sass: Compiles the CSS'); + console.log('gulp javascript: Compiles the JavaScript'); + done(); +}); + +/** + * See the USWDS + */ +gulp.task("sass", (done) => { + const pluginsProcess = [discardComments(), autoprefixer()]; + const pluginsMinify = [csso({ forceMediaMerge: false })]; + const dest = "./content/public/assets/css"; + gulp + .src('./content/assets/sass/main.scss') + .pipe(sourcemaps.init({ largeFile: true })) + .pipe( + sass({ outputStyle: "expanded" }) + .on("error", () => { console.log('ERROR: while compiling Sass') })) + .pipe(postcss(pluginsProcess)) + .pipe(gulp.dest(dest)) + .pipe(postcss(pluginsMinify)) + .pipe(rename({suffix: ".min"})) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(dest)); + done(); +}); + +gulp.task("watch", () => { + gulp.watch("./content/assets/**/*.scss", gulp.series("sass")); + return; +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ff7994a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8514 @@ +{ + "name": "site-joshbruce", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "site-joshbruce", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "gulp-dart-scss": "^1.1.0", + "gulp-postcss": "^9.0.1", + "gulp-rename": "^2.0.0", + "gulp-sourcemaps": "^3.0.0", + "postcss-csso": "^5.0.1", + "postcss-discard-comments": "^5.0.1" + }, + "devDependencies": { + "autoprefixer": "^10.4.0", + "gulp": "^4.0.2", + "sass": "^1.43.4" + } + }, + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "dependencies": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dependencies": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz", + "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==", + "dev": true, + "dependencies": { + "browserslist": "^4.17.5", + "caniuse-lite": "^1.0.30001272", + "fraction.js": "^4.1.1", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/autoprefixer/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browserslist": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.5.tgz", + "integrity": "sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001271", + "electron-to-chromium": "^1.3.878", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/browserslist/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001272", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001272.tgz", + "integrity": "sha512-DV1j9Oot5dydyH1v28g25KoVm7l8MTxazwuiH3utWiAS6iL/9Nh//TGwqFEeqqN8nnWYQ8HHhUq+o4QPt9kvYw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css/node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/debug-fabulous/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/each-props/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.880", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.880.tgz", + "integrity": "sha512-iwIP/6WoeSimzUKJIQtjtpVDsK8Ir8qQCMXsUBwg+rxJR2Uh3wTNSbxoYRfs+3UWx/9MAnPIxVZCyWkm8MT0uw==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "dependencies": { + "type": "^2.5.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fraction.js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.1.tgz", + "integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-dart-scss": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-dart-scss/-/gulp-dart-scss-1.1.0.tgz", + "integrity": "sha512-4Ib/nMKnnzAdLi0j+xZzY92jF8QCUrZLj/45Iqvx4PkS1lwV3vSp2vmtCHlUhd/i/w8FFmFbiPN/wWPRL8/qxQ==", + "dependencies": { + "plugin-error": "^1.0.1", + "sass": "^1.26.11", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "engines": { + "node": ">=7" + } + }, + "node_modules/gulp-postcss": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-9.0.1.tgz", + "integrity": "sha512-9QUHam5JyXwGUxaaMvoFQVT44tohpEFpM8xBdPfdwTYGM0AItS1iTQz0MpsF8Jroh7GF5Jt2GVPaYgvy8qD2Fw==", + "dependencies": { + "fancy-log": "^1.3.3", + "plugin-error": "^1.0.1", + "postcss-load-config": "^3.0.0", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dependencies": { + "import-from": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", + "dev": true + }, + "node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", + "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-iterator/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, + "node_modules/memoizee/node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "dev": true, + "optional": true + }, + "node_modules/nanoid": { + "version": "3.1.30", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", + "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/plugin-error/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.3.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.11.tgz", + "integrity": "sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==", + "peer": true, + "dependencies": { + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^0.6.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-csso": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-csso/-/postcss-csso-5.0.1.tgz", + "integrity": "sha512-TI99uhhJK2L5IQXPuyNcT3XV9bffSvDPmNpHi5f+tBq+R/01ucgLsUCcTDcVNqKMxO28klI6NwgjrHrM99x9KA==", + "dependencies": { + "csso": "^4.0.2" + }, + "engines": { + "node": ">=10.12.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", + "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", + "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "dependencies": { + "import-cwd": "^3.0.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "node_modules/postcss/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "peer": true + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/sass": { + "version": "1.43.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.43.4.tgz", + "integrity": "sha512-/ptG7KE9lxpGSYiXn7Ar+lKOv37xfWsZRtFYal2QHNigyVQDx685VFT/h7ejVr+R8w7H4tmUgtulsKl5YpveOg==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/sass/node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/sass/node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/sass/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sass/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/sass/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "node_modules/yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + }, + "dependencies": { + "@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "requires": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autoprefixer": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz", + "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==", + "dev": true, + "requires": { + "browserslist": "^4.17.5", + "caniuse-lite": "^1.0.30001272", + "fraction.js": "^4.1.1", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + } + } + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "browserslist": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.5.tgz", + "integrity": "sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001271", + "electron-to-chromium": "^1.3.878", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + } + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001272", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001272.tgz", + "integrity": "sha512-DV1j9Oot5dydyH1v28g25KoVm7l8MTxazwuiH3utWiAS6iL/9Nh//TGwqFEeqqN8nnWYQ8HHhUq+o4QPt9kvYw==", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "requires": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + } + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "requires": { + "css-tree": "^1.1.2" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "electron-to-chromium": { + "version": "1.3.880", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.880.tgz", + "integrity": "sha512-iwIP/6WoeSimzUKJIQtjtpVDsK8Ir8qQCMXsUBwg+rxJR2Uh3wTNSbxoYRfs+3UWx/9MAnPIxVZCyWkm8MT0uw==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "requires": { + "type": "^2.5.0" + }, + "dependencies": { + "type": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fraction.js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.1.tgz", + "integrity": "sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + } + }, + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + } + }, + "gulp-dart-scss": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-dart-scss/-/gulp-dart-scss-1.1.0.tgz", + "integrity": "sha512-4Ib/nMKnnzAdLi0j+xZzY92jF8QCUrZLj/45Iqvx4PkS1lwV3vSp2vmtCHlUhd/i/w8FFmFbiPN/wWPRL8/qxQ==", + "requires": { + "plugin-error": "^1.0.1", + "sass": "^1.26.11", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "gulp-postcss": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-9.0.1.tgz", + "integrity": "sha512-9QUHam5JyXwGUxaaMvoFQVT44tohpEFpM8xBdPfdwTYGM0AItS1iTQz0MpsF8Jroh7GF5Jt2GVPaYgvy8qD2Fw==", + "requires": { + "fancy-log": "^1.3.3", + "plugin-error": "^1.0.1", + "postcss-load-config": "^3.0.0", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==" + }, + "gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "requires": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "requires": { + "import-from": "^3.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "requires": { + "resolve-from": "^5.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "just-debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "lilconfig": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", + "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==" + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "dependencies": { + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "dev": true, + "optional": true + }, + "nanoid": { + "version": "3.1.30", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", + "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "peer": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "8.3.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.11.tgz", + "integrity": "sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA==", + "peer": true, + "requires": { + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^0.6.2" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "peer": true + } + } + }, + "postcss-csso": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-csso/-/postcss-csso-5.0.1.tgz", + "integrity": "sha512-TI99uhhJK2L5IQXPuyNcT3XV9bffSvDPmNpHi5f+tBq+R/01ucgLsUCcTDcVNqKMxO28klI6NwgjrHrM99x9KA==", + "requires": { + "csso": "^4.0.2" + } + }, + "postcss-discard-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", + "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "requires": {} + }, + "postcss-load-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", + "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "requires": { + "import-cwd": "^3.0.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "sass": { + "version": "1.43.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.43.4.tgz", + "integrity": "sha512-/ptG7KE9lxpGSYiXn7Ar+lKOv37xfWsZRtFYal2QHNigyVQDx685VFT/h7ejVr+R8w7H4tmUgtulsKl5YpveOg==", + "requires": { + "chokidar": ">=3.0.0 <4.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "peer": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "requires": { + "source-map": "^0.5.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, + "yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..a11a3981 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "site-joshbruce", + "version": "1.0.0", + "description": "For compiling Sass and JS for joshbruce.com", + "main": "gulpfile.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Josh Bruce", + "license": "MIT", + "dependencies": { + "gulp-dart-scss": "^1.1.0", + "gulp-postcss": "^9.0.1", + "gulp-rename": "^2.0.0", + "gulp-sourcemaps": "^3.0.0", + "postcss-csso": "^5.0.1", + "postcss-discard-comments": "^5.0.1" + }, + "devDependencies": { + "autoprefixer": "^10.4.0", + "gulp": "^4.0.2", + "sass": "^1.43.4" + } +} diff --git a/public/index.php b/public/index.php index 579db12c..5bf35bf0 100644 --- a/public/index.php +++ b/public/index.php @@ -12,85 +12,9 @@ // Inject environment variables to global $_SERVER array Dotenv\Dotenv::createImmutable($projectRoot)->load(); -$server = JoshBruce\Site\SiteDynamic\Server::init($_SERVER, $projectRoot); - -if ($server->isMissingRequiredValues()) { - JoshBruce\Site\SiteDynamic\Emitter::emitInteralServerErrorResponse( - JoshBruce\Site\Content\Markdown::markdownConverter(), - $projectRoot - ); - exit; -} - -if ($server->isRequestingUnsupportedMethod()) { - JoshBruce\Site\SiteDynamic\Emitter::emitUnsupportedMethodResponse( - JoshBruce\Site\Content\Markdown::markdownConverter(), - $projectRoot, - $server - ); - exit; -} - -$fileSystem = JoshBruce\Site\FileSystem::init( - $server->contentRoot(), - $server->requestUriWithoutFileName(), - $server->requestFileName() -); - -if ($fileSystem->rootFolderIsMissing()) { - JoshBruce\Site\SiteDynamic\Emitter::emitBadContentResponse( - JoshBruce\Site\Content\Markdown::markdownConverter(), - $projectRoot - ); - exit; -} - -// TESTING: Redirection -// Check browser address becomes /design-your-life -// if ($server->requestUriWithoutFileName() !== '/design-your-life') { -// $_SERVER['REQUEST_URI'] = '/self-improvement'; -// $server = JoshBruce\Site\Server::init($_SERVER, $projectRoot); -// } - -$fileSystem = $fileSystem->with( - folderPath: $server->requestUriWithoutFileName(), - fileName: $server->requestFileName() -); - -if ($fileSystem->notFound()) { - $fileSystem = $fileSystem->with('/', 'error-404.md'); - JoshBruce\Site\SiteDynamic\Emitter::emitNotFoundResponse( - JoshBruce\Site\Content\Markdown::markdownConverter(), - $fileSystem - ); - exit; -} - -if ($server->isRequestingFile()) { - JoshBruce\Site\SiteDynamic\Emitter::emitFile( - $fileSystem->mimeType(), - $fileSystem->path() - ); - exit; -} - -$markdown = JoshBruce\Site\Content\Markdown::init($fileSystem); -if ($markdown->hasMoved()) { - $location = $server->domain() . $markdown->redirectPath(); - JoshBruce\Site\SiteDynamic\Emitter::emitRedirectionResponse($location); - exit; -} - -$page = JoshBruce\Site\Pages\DefaultTemplate::create( - JoshBruce\Site\Content\Markdown::init($fileSystem)->convert(), - $fileSystem->mimeType(), - $fileSystem->folderStack(), - $fileSystem->contentRoot() -); - -JoshBruce\Site\SiteDynamic\Emitter::emitWithResponse( - 200, - $page->headers(), - $page->body() +JoshBruce\Site\SiteDynamic\Emitter::emit( + response:JoshBruce\Site\HttpResponse::from( + request: JoshBruce\Site\HttpRequest::fromGlobals() + ) ); exit; diff --git a/setup-errors/500_2.md b/setup-errors/500_2.md deleted file mode 100644 index e2c0fb8e..00000000 --- a/setup-errors/500_2.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Server error -usage: The folder containing the site content could not be located. ---- - -# 500: Server error (content) - -We're not sure what happened here but we're pretty sure it's on us. - -Please try again later. - -If this error persists, please contact [Josh Bruce](https://github.com/joshbruce). diff --git a/site-dynamic-php/.htaccess b/site-dynamic-php/.htaccess new file mode 100644 index 00000000..55a55baf --- /dev/null +++ b/site-dynamic-php/.htaccess @@ -0,0 +1,28 @@ +Options -Indexes + +ErrorDocument 404 /error-404.html +ErrorDocument 405 /error-405.html +ErrorDocument 500 /error-500.html + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + + + ExpiresActive On + ExpiresDefault "access plus 5 seconds" + diff --git a/site-dynamic-php/index.php b/site-dynamic-php/index.php new file mode 100644 index 00000000..754bec6d --- /dev/null +++ b/site-dynamic-php/index.php @@ -0,0 +1,17 @@ +load(); + +JoshBruce\Site\SiteDynamic\Emitter::emit( + response:JoshBruce\Site\HttpResponse::from( + request: JoshBruce\Site\HttpRequest::fromGlobals() + ) +); +exit; diff --git a/src/Content/FrontMatter.php b/src/Content/FrontMatter.php index a95be3e2..ca49655e 100644 --- a/src/Content/FrontMatter.php +++ b/src/Content/FrontMatter.php @@ -17,7 +17,7 @@ public static function init(array $frontMatter = []): FrontMatter /** * @param array $frontMatter */ - public function __construct(private array $frontMatter = []) + private function __construct(private array $frontMatter = []) { } @@ -34,40 +34,24 @@ public function title(): string return ''; } - public function header(): string - { - if ($this->hasMember('header')) { - return strval($this->frontMatter['header']); - } - return ''; - } - /** - * @return array + * @return array */ - public function navigation(): array + public function data(): array { - if ($this->hasMember('navigation')) { - return $this->frontMatter['navigation']; + if ($this->hasMember('data')) { + return $this->frontMatter['data']; } return []; } - public function redirectPath(): string - { - if ($this->hasMember('redirect')) { - return strval($this->frontMatter['redirect']); - } - return ''; - } - /** - * @return array + * @return array */ - public function data(): array + public function dateblock(): array { - if ($this->hasMember('data')) { - return $this->frontMatter['data']; + if ($this->hasMember('dateblock')) { + return $this->frontMatter['dateblock']; } return []; } @@ -79,36 +63,4 @@ public function original(): string } return ''; } - - public function type(): string - { - if ($this->hasMember('type')) { - return strval($this->frontMatter['type']); - } - return ''; - } - - public function created(): int|false - { - if ($this->hasMember('created')) { - return intval($this->frontMatter['created']); - } - return false; - } - - public function updated(): int|false - { - if ($this->hasMember('updated')) { - return intval($this->frontMatter['updated']); - } - return false; - } - - public function moved(): int|false - { - if ($this->hasMember('moved')) { - return intval($this->frontMatter['moved']); - } - return false; - } } diff --git a/src/Content/Markdown.php b/src/Content/Markdown.php index a17b05d7..ce5b7c9a 100644 --- a/src/Content/Markdown.php +++ b/src/Content/Markdown.php @@ -4,30 +4,27 @@ namespace JoshBruce\Site\Content; -use DirectoryIterator; - use Eightfold\Markdown\Markdown as MarkdownConverter; -use JoshBruce\Site\FileSystem; +use JoshBruce\Site\File; use JoshBruce\Site\PageComponents\Data; use JoshBruce\Site\PageComponents\DateBlock; -use JoshBruce\Site\PageComponents\Heading; use JoshBruce\Site\PageComponents\LogList; use JoshBruce\Site\PageComponents\OriginalContentNotice; -use JoshBruce\Site\Content\FrontMatter; - class Markdown { - private string $markdown = ''; + private string $fileContent = ''; /** * @var FrontMatter */ private FrontMatter $frontMatter; - public static function init(FileSystem $file): Markdown + private string $body = ''; + + public static function for(File $file): Markdown { return new Markdown($file); } @@ -39,6 +36,7 @@ public static function markdownConverter(): MarkdownConverter ->smartPunctuation() ->withConfig(['html_input' => 'allow']) ->descriptionLists() + ->attributes() ->abbreviations() ->externalLinks([ 'open_in_new_window' => true, @@ -51,80 +49,46 @@ public static function markdownConverter(): MarkdownConverter ); } - public function __construct(private FileSystem $file) + private function __construct(private File $file) { } - public function convert(): string + public function html(): string { - // TODO: cache as property - $body = self::markdownConverter()->getBody($this->markdown()); - - if ($this->frontMatter()->hasMember('data')) { - $body = Data::create(data: $this->frontMatter()->data()) . - "\n\n" . $body; - } + $body = $this->body(); - $originalLink = ''; - $original = $this->file->messages('original.md'); - if ( - $this->frontMatter()->hasMember('original') and - $original->found() - ) { - $copyContent = file_get_contents($original->path()); - if (is_string($copyContent)) { - $originalLink = OriginalContentNotice::create( - copyContent: $copyContent, - messagePath: $original->path(), - originalLink: $this->frontMatter()->original() - ); - } - } + $inserts = []; + if (preg_match_all('/{!!(.*)!!}/', $body, $inserts)) { + $templateMap = [ + 'data' => Data::class, + 'dateblock' => DateBlock::class, + 'loglist' => LogList::class, + 'original' => OriginalContentNotice::class + ]; - $body = $originalLink . "\n\n" . $body; + $replacements = $inserts[0]; + $templates = $inserts[1]; + for ($i = 0; $i < count($replacements); $i++) { + $templateKey = $templates[$i]; + if (! array_key_exists($templateKey, $templateMap)) { + continue; + } - $dateBlock = DateBlock::create(frontMatter: $this->frontMatter()); - if (strlen($dateBlock) > 0) { - $body = $dateBlock . "\n\n" . $body; - } + $b = ''; + $template = $templateMap[$templateKey]; + if ($templateKey === 'loglist') { + $b = $template::create($this->file); - if ($this->file->isNotRoot()) { - // TODO: Not sure why this isn't working for static site - $body = Heading::create(frontMatter: $this->frontMatter()) . - "\n\n" . $body; - } - - if ( - $this->frontMatter()->hasMember('type') and - $this->frontMatter()->type() === 'log' - ) { - $body = $body . "\n\n" . LogList::create( - $this->file->subfolders('content.md') - ); - } - - return self::markdownConverter()->convert($body); - } + } else { + $b = $template::create($this->frontMatter()); - public function markdown(): string - { - if (strlen($this->markdown) === 0 and $this->file->found()) { - $fileName = 'content.md'; - if (strlen($this->file->fileName()) > 0) { - $fileName = $this->file->fileName(); - } + } - $markdown = file_get_contents( - $this->file->fileNamed($fileName)->path() - ); - if (is_bool($markdown)) { - $markdown = ''; + $body = str_replace($replacements[$i], $b, $body); } - - $this->markdown = $markdown; } - return $this->markdown; + return self::markdownConverter()->convert($body); } /** @@ -134,19 +98,44 @@ public function frontMatter(): FrontMatter { if (! isset($this->frontMatter)) { $frontMatter = self::markdownConverter() - ->getFrontMatter($this->markdown()); + ->getFrontMatter($this->fileContent()); $this->frontMatter = FrontMatter::init($frontMatter); } return $this->frontMatter; } - public function hasMoved(): bool + public function body(): string { - return strlen($this->redirectPath()) > 0; + if (strlen($this->body) === 0) { + $this->body = self::markdownConverter() + ->getBody($this->fileContent()); + } + return $this->body; } - public function redirectPath(): string + public function pageTitle(): string { - return $this->frontMatter()->redirectPath(); + $titles = []; + $titles[] = $this->frontMatter()->title(); + + $file = clone $this->file; + while ($file->canGoUp()) { + $file = $file->up(); + + $m = Markdown::for($file); + + $titles[] = $m->frontMatter()->title(); + } + + $titles = array_filter($titles); + return implode(' | ', $titles); + } + + private function fileContent(): string + { + if (strlen($this->fileContent) === 0 and $this->file->found()) { + $this->fileContent = $this->file->contents(); + } + return $this->fileContent; } } diff --git a/src/File.php b/src/File.php new file mode 100644 index 00000000..fa5af6c1 --- /dev/null +++ b/src/File.php @@ -0,0 +1,139 @@ +isMarkdown(); + } + + public function isMarkdown(): bool + { + $parts = explode('/', $this->localPath); + $possibleFileName = array_pop($parts); + return str_ends_with($possibleFileName, '.md'); + } + + public function isHtml(): bool + { + $parts = explode('/', $this->localPath); + $possibleFileName = array_pop($parts); + return str_ends_with($possibleFileName, '.html'); + } + + public function found(): bool + { + return file_exists($this->path()) and is_file($this->path()); + } + + public function isNotFound(): bool + { + return ! $this->found(); + } + + /** + * @todo: move to trait + */ + public function path(bool $full = true): string + { + if ($full) { + return $this->localPath; + } + // TODO: test and verify used - returning empty string not an option. + return str_replace( + $this->contentRoot(), + '', + $this->localPath + ); + } + + public function canGoUp(): bool + { + return $this->path(false) !== '/content.md'; + } + + public function up(): File + { + $parts = explode('/', $this->localPath); + $parts = array_slice($parts, 0, -2); // remove file name and one folder. + $localPath = implode('/', $parts); + return File::at($localPath . '/content.md'); + } + + public function contents(): string + { + $contents = file_get_contents($this->path()); + if ($contents === false) { + return ''; + } + return $contents; + } + + public function mimetype(): string + { + $type = mime_content_type($this->path()); + if (is_bool($type) and $type === false) { + return ''; + } + + if ($type === 'text/plain') { + $extensionMap = [ + 'md' => 'text/html', + 'css' => 'text/css', + 'js' => 'text/javascript' + ]; + + $parts = explode('.', $this->path()); + $extension = array_pop($parts); + + $type = $extensionMap[$extension]; + } + return $type; + } + + /** + * @return File[] + */ + public function children(string $filesNamed): array + { + $base = str_replace('/content.md', '', $this->path()); + + $files = []; + foreach (new DirectoryIterator($base) as $folder) { + if ($folder->isFile() or $folder->isDot()) { + continue; + } + + $fullPathToFolder = $folder->getPathname(); + $parts = explode('/', $fullPathToFolder); + $folderName = array_pop($parts); + + $files[$folderName] = File::at( + $fullPathToFolder . '/' . $filesNamed + ); + } + return $files; + } + + private function contentRoot(): string + { + return FileSystem::contentRoot() . '/public'; + } +} diff --git a/src/FileSystem.php b/src/FileSystem.php index e4894566..9af3e1fe 100644 --- a/src/FileSystem.php +++ b/src/FileSystem.php @@ -4,221 +4,24 @@ namespace JoshBruce\Site; -use DirectoryIterator; - -use JoshBruce\Site\Content\FrontMatter; - -/** - * @todo: Change contentRoot to be the folder in which the text-based content is. - */ class FileSystem { - public static function init( - string $contentRoot, - string $folderPath = '/', - string $fileName = '' - ): FileSystem { - return new FileSystem( - $contentRoot, - $folderPath, - $fileName - ); - } - - public function __construct( - private string $contentRoot, - private string $folderPath = '/', - private string $fileName = '' - ) { - } - - /** - * @return string Path to where the text-based content for the site lives. - */ - public function contentRoot(): string - { - return $this->contentRoot; - } - - public function with(string $folderPath, string $fileName = ''): FileSystem - { - return new self($this->contentRoot, $folderPath, $fileName); - } - - public function fileNamed(string $fileName): FileSystem - { - return $this->with($this->folderPath, $fileName); - } - - public function fileName(): string - { - return $this->fileName; - } - - public function path(bool $full = true): string - { - $path = $this->contentRoot . $this->folderPath . '/' . $this->fileName(); - if (! $full) { - $path = str_replace($this->contentRoot, '', $path); - } - - if (str_ends_with($path, '/')) { - return substr($path, 0, -1); - } - return $path; - } - - public function mimetype(): string - { - $type = mime_content_type($this->path()); - if (is_bool($type) and $type === false) { - return ''; - } - - if ($type === 'text/plain') { - $extensionMap = [ - 'md' => 'text/html', - 'css' => 'text/css', - 'js' => 'text/javascript' - ]; - - $parts = explode('.', $this->path()); - $extension = array_pop($parts); - - $type = $extensionMap[$extension]; - } - return $type; - } - - public function navigation(string $file = ''): FileSystem - { - return $this->up()->with('/navigation', $file); - } - - public function messages(string $file = ''): FileSystem - { - return $this->up()->with('/messages', $file); - } - - public function media(string $file = ''): FileSystem - { - return $this->up()->with('/media', $file); - } - - public function assets(string $file = ''): FileSystem + public static function contentRoot(): string { - return $this->up()->with('/assets', $file); - } - - public function rootFolderIsMissing(): bool - { - if (! file_exists($this->contentRoot())) { - return true; - } - return ! is_dir($this->contentRoot()); - } - - public function notFound(): bool - { - return ! $this->found(); - } - - public function found(): bool - { - return file_exists($this->path()); - } - - public function isNotRoot(): bool - { - return ! $this->isRoot(); - } - - public function isRoot(): bool - { - $subtract = str_replace( - [$this->contentRoot(), '/content.md'], - ['', ''], - $this->path() - ); - return strlen($subtract) === 0 or $subtract === '/'; - } - - public function isFile(): bool - { - return file_exists($this->path()) and ! is_dir($this->path()); - } - - /** - * @return FileSystem[] - */ - public function folderStack(string $fileName = ''): array - { - $folderPath = $this->path(); - if (! is_dir($folderPath)) { - return []; - } - $folderPath = str_replace($this->contentRoot(), '', $folderPath); - - $folderPathParts = explode('/', $folderPath); - - $folders = []; - while (count($folderPathParts) > 0) { - $path = implode('/', $folderPathParts); - - $clone = clone $this; - $clone = $clone->with(folderPath: $path, fileName: $fileName); - - $folders[] = $clone; - - array_pop($folderPathParts); - } - return $folders; - } - - /** - * @return array - */ - public function subfolders(string $fileName = ''): array - { - $folderPath = $this->path(); - if (! is_dir($folderPath) and ! is_file($folderPath)) { - return []; - - } elseif (is_file($folderPath)) { - $parts = explode('/', $folderPath); - array_pop($parts); - $folderPath = implode('/', $parts); - - } - - $content = []; - foreach (new DirectoryIterator($folderPath) as $folder) { - if ($folder->isFile() or $folder->isDot()) { - continue; - } - - $fullPathToFolder = $folder->getPathname(); - $partialPath = str_replace( - $this->contentRoot(), - '', - $fullPathToFolder - ); - - $parts = explode('/', $partialPath); - - $folderName = array_pop($parts); - - $clone = clone $this; - $content[$folderName] = $clone->with($partialPath, $fileName); + $parts = explode('/', self::projectRoot()); + $parts[] = 'content'; + $base = implode('/', $parts); + if (str_ends_with($base, '/')) { + $base = substr($base, 0, -1); } - return $content; + return $base; } - private function up(): FileSystem + public static function projectRoot(): string { - $parts = explode('/', $this->contentRoot); - array_pop($parts); - $newRoot = implode('/', $parts); - return FileSystem::init($newRoot); + $dir = __DIR__; + $parts = explode('/', $dir); + $parts = array_slice($parts, 0, -1); + return implode('/', $parts); } } diff --git a/src/HttpRequest.php b/src/HttpRequest.php new file mode 100644 index 00000000..b32a13c3 --- /dev/null +++ b/src/HttpRequest.php @@ -0,0 +1,156 @@ +serverGlobals()->isMissingAppEnv()) { + return true; + } + + if ($this->serverGlobals()->appEnvIsNot('production')) { + // use Whoops! for error display + $errorHandler = new ErrorHandler(); + $errorHandler->pushHandler( + new ErrorPageHandler() + ); + $errorHandler->register(); + } + return false; + } + + public function isUnsupportedMethod(): bool + { + return ! $this->isSupportedMethod(); + } + + public function isNotFound(): bool + { + return ! $this->isFound(); + } + + public function isFile(): bool + { + return str_contains($this->possibleFileName(), '.'); + } + + public function localFile(): File + { + return File::at(localPath: $this->localPath()); + } + + private function isFound(): bool + { + return file_exists($this->localPath()) and is_file($this->localPath()); + } + + private function localPath(): string + { + if (empty($this->localPath)) { + $possibleFileName = $this->possibleFileName(); + $relativePath = $this->uriPath(); + if (empty($possibleFileName)) { + $relativePath = $this->uriPath() . '/content.md'; + } + + $root = FileSystem::contentRoot(); + + $this->localPath = "{$root}/public{$relativePath}"; + } + return $this->localPath; + } + + private function possibleFileName(): string + { + $parts = explode('/', $this->uriPath()); + $lastPart = array_slice($parts, -1); + $possibleFileName = array_shift($lastPart); + if ( + $possibleFileName === null or + ! str_contains($possibleFileName, '.') + ) { + return ''; + } + return $possibleFileName; + } + + /** + * @return string[] + */ + private function supportedMethods(): array + { + return ['GET']; + } + + private function isSupportedMethod(): bool + { + $requestMethod = strtoupper($this->psrRequest()->getMethod()); + return in_array($requestMethod, $this->supportedMethods()); + } + + private function serverGlobals(): ServerGlobals + { + return ServerGlobals::init(); + } + + private function psrRequest(): RequestInterface + { + if (! isset($this->psrRequest)) { + $psr17Factory = new PsrFactory(); + $creator = new PsrServerRequestCreator( + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory + ); + + $this->psrRequest = $creator->fromGlobals(); + } + return $this->psrRequest; + } + + private function uriPath(): string + { + $uriPath = $this->uri()->getPath(); + if ($uriPath === '/') { + $uriPath = ''; + } + return $uriPath; + } + + private function uri(): UriInterface + { + return $this->psrRequest()->getUri(); + } +} diff --git a/src/HttpResponse.php b/src/HttpResponse.php new file mode 100644 index 00000000..90eaa3ad --- /dev/null +++ b/src/HttpResponse.php @@ -0,0 +1,168 @@ +request->isMissingRequiredValues()) { + return 500; + + } elseif ($this->request->isUnsupportedMethod()) { + return 405; + + } elseif ($this->request->isNotFound()) { + return 404; + + } + return 200; + } + + /** + * @return array + */ + public function headers(): array + { + $headers = []; + if ($this->statusCode() === 200) { + $headers['Content-Type'] = $this->request->localFile()->mimeType(); + + } elseif ($this->statusCode() === 404) { + $headers['Content-Type'] = 'text/html'; + + } + + return $headers; + } + + public function body(): string + { + $localFile = $this->request->localFile(); + if ($this->statusCode() === 200 and $localFile->isNotMarkdown()) { + return $localFile->path(); + + } elseif ($this->statusCode() === 404) { + $localPath = FileSystem::contentRoot() . '/public/error-404.md'; + $localFile = File::at($localPath); + + } elseif ($this->statusCode() === 405) { + $localPath = FileSystem::contentRoot() . '/public/error-405.md'; + $localFile = File::at($localPath); + + } + + $html = ''; + $pageTitle = ''; + if ($localFile->isMarkdown()) { + $markdown = Markdown::for(file: $localFile); + $pageTitle = $markdown->pageTitle(); + $html = $markdown->html(); + + } + + return Document::create( + $pageTitle + )->head( + Element::meta()->omitEndTag()->props( + 'name viewport', + 'content width=device-width,initial-scale=1' + ), + Element::link()->omitEndTag()->props( + 'type image/x-icon', + 'rel icon', + 'href /assets/favicons/favicon.ico' + ), + Element::link()->omitEndTag()->props( + 'rel apple-touch-icon', + 'href /assets/favicons/apple-touch-icon.png', + 'sizes 180x180' + ), + Element::link()->omitEndTag()->props( + 'rel image/png', + 'href /assets/favicons/favicon-32x32.png', + 'sizes 32x32' + ), + Element::link()->omitEndTag()->props( + 'rel image/png', + 'href /assets/favicons/favicon-16x16.png', + 'sizes 16x16' + ), + $this->cssElement() + )->body( + Element::a('menu')->props('href #main-nav', 'id content-top'), + Element::article( + $html + )->props('typeof BlogPosting', 'vocab https://schema.org/'), + Element::a('top')->props('href #content-top', 'id go-to-top'), + Navigation::create('main.md'), + Element::footer( + Element::p( + 'Copyright © 2004–' . date('Y') . ' Joshua C. Bruce. ' . + 'All rights reserved.' + ) + ) + )->build(); + } + + public function cssElement(): Element + { + $cssPath = '/assets/css/main.min.css'; + // $filePath = $contentRoot . $cssPath; + // TODO: should be last commit of CSS file - another reason to place + // content in same folder as rest of project. + $query = round(microtime(true)); + + return Element::link()->omitEndTag() + ->props('rel stylesheet', "href {$cssPath}?v={$query}"); + } + + public function psrResponse(): ResponseInterface + { + if (! isset($this->psrResponse)) { + $psr17Factory = new PsrFactory(); + $body = $this->body(); + $stream = $psr17Factory->createStream($body); + if ($this->request->isFile()) { + $stream = $psr17Factory->createStreamFromFile($body); + } + + $this->psrResponse = new PsrResponse( + $this->statusCode(), + $this->headers(), + $stream + ); + } + return $this->psrResponse; + } +} diff --git a/src/PageComponents/Data.php b/src/PageComponents/Data.php index 2e5603dc..3335ba55 100644 --- a/src/PageComponents/Data.php +++ b/src/PageComponents/Data.php @@ -4,19 +4,16 @@ namespace JoshBruce\Site\PageComponents; -use Carbon\Carbon; - -use Eightfold\HTMLBuilder\Element as HtmlElement; +use Eightfold\HTMLBuilder\Element; use JoshBruce\Site\Content\FrontMatter; class Data { - /** - * @param array> $data - */ - public static function create(array $data): HtmlElement|string + public static function create(FrontMatter $frontMatter): string { + $data = $frontMatter->data(); + $listHeadings = []; foreach ($data as $row) { $label = $row[0]; @@ -33,26 +30,26 @@ public static function create(array $data): HtmlElement|string } - $listHeadings[] = Htmlelement::li( + $listHeadings[] = Element::li( $label . " ({$detail})", - HtmlElement::ul( - HtmlElement::li( - HtmlElement::b('current: '), + Element::ul( + Element::li( + Element::b('current: '), $current ), - HtmlElement::li( - HtmlElement::abbr('min')->props('title minimum'), + Element::li( + Element::abbr('min')->props('title minimum'), ': ', $low ), - HtmlElement::li( - HtmlElement::abbr('max')->props('title maximum'), + Element::li( + Element::abbr('max')->props('title maximum'), ': ', $high ) ) ); } - return HtmlElement::ul(...$listHeadings); + return Element::ul(...$listHeadings)->build(); } } diff --git a/src/PageComponents/DateBlock.php b/src/PageComponents/DateBlock.php index 1fdd0298..d37277ad 100644 --- a/src/PageComponents/DateBlock.php +++ b/src/PageComponents/DateBlock.php @@ -6,7 +6,7 @@ use Carbon\Carbon; -use Eightfold\HTMLBuilder\Element as HtmlElement; +use Eightfold\HTMLBuilder\Element; use JoshBruce\Site\Content\FrontMatter; @@ -14,43 +14,43 @@ class DateBlock { public static function create(FrontMatter $frontMatter): string { - $created = self::timestamp( - 'Created on', - $frontMatter->created(), - 'dateCreated' - ); - - $moved = self::timestamp('Moved on', $frontMatter->moved()); - - $updated = self::timestamp( - 'Updated on', - $frontMatter->updated(), - 'dateModified' - ); + $times = []; + $dateblock = $frontMatter->dateblock(); + foreach ($dateblock as $date) { + list($d, $label) = explode(' ', $date, 2); + $schemaProp = ''; + if (str_starts_with($label, 'Created')) { + $schemaProp = 'dateCreated'; + + } elseif (str_starts_with($label, 'Updated')) { + $schemaProp = 'dateModified'; + + } + $times[] = self::timestamp($label, intval($d), $schemaProp); + } - if (empty($updated) and empty($moved) and empty($created)) { + if (count($times) === 0) { return ''; } - return HtmlElement::div($created, $moved, $updated) - ->props('is dateblock')->build(); + return Element::div(...$times)->props('is dateblock')->build(); } private static function timestamp( string $label, int|false $date = false, string $schemaProp = '' - ): HtmlElement|string { + ): Element|string { if (! $date) { return ''; } if ($carbon = Carbon::createFromFormat('Ymd', strval($date))) { - $time = HtmlElement::time($carbon->toFormattedDateString()) + $time = Element::time($carbon->toFormattedDateString()) ->props( (strlen($schemaProp) > 0) ? "property {$schemaProp}" : '', 'content ' . $carbon->format('Y-m-d') )->build(); - return HtmlElement::p("{$label}: {$time}"); + return Element::p("{$label}: {$time}"); } return ''; } diff --git a/src/PageComponents/Favicons.php b/src/PageComponents/Favicons.php deleted file mode 100644 index 83286e33..00000000 --- a/src/PageComponents/Favicons.php +++ /dev/null @@ -1,39 +0,0 @@ -props( - 'type image/x-icon', - 'rel icon', - 'href /assets/favicons/favicon.ico' - ), - HtmlElement::link()->props( - 'rel apple-touch-icon', - 'href /assets/favicons/apple-touch-icon.png', - 'sizes 180x180' - ), - HtmlElement::link()->props( - 'rel image/png', - 'href /assets/favicons/favicon-32x32.png', - 'sizes 32x32' - ), - HtmlElement::link()->props( - 'rel image/png', - 'href /assets/favicons/favicon-16x16.png', - 'sizes 16x16' - ) - ]; - } -} diff --git a/src/PageComponents/Footer.php b/src/PageComponents/Footer.php deleted file mode 100644 index 38f1274d..00000000 --- a/src/PageComponents/Footer.php +++ /dev/null @@ -1,20 +0,0 @@ - - */ - public static function create(string $contentRoot): array - { - $headElements = [ - HtmlElement::meta() - ->props('name viewport', 'content width=device-width,initial-scale=1') - ]; - - $headElements = array_merge($headElements, Favicons::create()); - - $cssPath = '/assets/css/main.min.css'; - // $filePath = $contentRoot . $cssPath; - // TODO: should be last commit of CSS file - another reason to place - // content in same folder as rest of project. - $query = round(microtime(true)); - - $headElements[] = HtmlElement::link() - ->props('rel stylesheet', "href {$cssPath}?{$query}"); - return $headElements; - } -} diff --git a/src/PageComponents/Heading.php b/src/PageComponents/Heading.php deleted file mode 100644 index ad9128b9..00000000 --- a/src/PageComponents/Heading.php +++ /dev/null @@ -1,19 +0,0 @@ -hasMember('header')) { - return "# {$frontMatter->header()}"; - - } - return "# {$frontMatter->title()}"; - } -} diff --git a/src/PageComponents/LogList.php b/src/PageComponents/LogList.php index aa84f769..687571c5 100644 --- a/src/PageComponents/LogList.php +++ b/src/PageComponents/LogList.php @@ -4,21 +4,17 @@ namespace JoshBruce\Site\PageComponents; -use Eightfold\HTMLBuilder\Element as HtmlElement; +use JoshBruce\Site\File; -use JoshBruce\Site\FileSystem; -use JoshBruce\Site\Content\Markdown; +use Eightfold\HTMLBuilder\Element; -use JoshBruce\Site\Content\FrontMatter; +use JoshBruce\Site\Content\Markdown; class LogList { - /** - * @param FileSystem[] $fileSubfolders - */ - public static function create( - array $fileSubfolders - ): string { + public static function create(File $file): string + { + $fileSubfolders = $file->children(filesNamed: 'content.md'); if (count($fileSubfolders) === 0) { return ''; } @@ -27,7 +23,7 @@ public static function create( $logLinks = []; foreach ($fileSubfolders as $key => $file) { if (! str_starts_with(strval($key), '_') and $file->found()) { - $markdown = Markdown::init($file); + $markdown = Markdown::for($file); $linkPath = str_replace( '/content.md', @@ -35,13 +31,13 @@ public static function create( $file->path(full: false) ); - $logLinks[] = HtmlElement::li( - HtmlElement::a( - $markdown->frontMatter()->title() // ['title'] + $logLinks[] = Element::li( + Element::a( + $markdown->frontMatter()->title() )->props('href ' . $linkPath) ); } } - return HtmlElement::ul(...$logLinks)->build(); + return Element::ul(...$logLinks)->build(); } } diff --git a/src/PageComponents/Navigation.php b/src/PageComponents/Navigation.php index 35c096af..93925813 100644 --- a/src/PageComponents/Navigation.php +++ b/src/PageComponents/Navigation.php @@ -4,82 +4,27 @@ namespace JoshBruce\Site\PageComponents; -use Stringable; - -use Eightfold\XMLBuilder\Contracts\Buildable; -use Eightfold\HTMLBuilder\Element as HtmlElement; +use Eightfold\HTMLBuilder\Element; use JoshBruce\Site\FileSystem; +use JoshBruce\Site\File; + use JoshBruce\Site\Content\Markdown; -class Navigation implements Buildable, Stringable +class Navigation { - public static function create(string $contentRoot): Navigation - { - return new Navigation($contentRoot); - } - - public function __construct(private string $contentRoot) - { - } - - private function listItem(string $for): HtmlElement - { - return HtmlElement::li( - $this->anchor(for: $for) - ); - } - - private function anchor(string $for): HtmlElement - { - list($href, $text) = explode(' ', $for, 2); - return HtmlElement::a($text)->props('href ' . $href); - } - - /** - * @return array> - */ - private function navigation(): array - { - $file = FileSystem::init($this->contentRoot)->navigation('main.md'); - // $file = $this->file->with(folderPath: '/navigation', fileName: 'main.md'); - return Markdown::init(file: $file)->frontMatter()->navigation(); - } - - public function build(): string + public static function create(string $fileName): string { - $li = []; - $nav = $this->navigation(); - foreach ($nav as $item) { - // TODO: Not sure this constitutes an elegant solution, but I can't - // seem to find another way. Use backslash for commas. - $item = str_replace('\\', ',', $item); - if (is_string($item)) { - $li[] = $this->listItem(for: $item); - - } elseif (is_array($item)) { - $l = ''; - $s = []; - for ($i = 0; $i < count($item); $i++) { - if ($i === 0) { - $l = $this->anchor($item[$i]); - - } else { - $s[] = $this->listItem($item[$i]); - - } - } - - $li[] = HtmlElement::li($l, HtmlElement::ul(...$s)); - } + $contentRoot = FileSystem::contentRoot(); + $navigationPath = $contentRoot . '/navigation'; + $filePath = $navigationPath . '/' . $fileName; + $file = File::at($filePath); + if ($file->isNotFound()) { + return ''; } - return HtmlElement::nav( - HtmlElement::ul(...$li) - )->props('id main-nav')->build(); - } - public function __toString(): string - { - return $this->build(); + $html = Markdown::for($file)->html(); + + return Element::nav($html)->props('id main-nav')->build(); } } diff --git a/src/PageComponents/OriginalContentNotice.php b/src/PageComponents/OriginalContentNotice.php index 51a53fe5..067671a2 100644 --- a/src/PageComponents/OriginalContentNotice.php +++ b/src/PageComponents/OriginalContentNotice.php @@ -4,36 +4,42 @@ namespace JoshBruce\Site\PageComponents; -use Eightfold\Markdown\Markdown as MarkdownConverter; - -use Eightfold\HTMLBuilder\Element as HtmlElement; +use Eightfold\HTMLBuilder\Element; +use JoshBruce\Site\File; use JoshBruce\Site\FileSystem; + use JoshBruce\Site\Content\Markdown; use JoshBruce\Site\Content\FrontMatter; class OriginalContentNotice { - public static function create( - string $copyContent, - string $messagePath, - string $originalLink - ): string { - if (empty($copyContent)) { + public static function create(FrontMatter $frontMatter): string + { + $contentRoot = FileSystem::contentRoot(); + $noticesRoot = $contentRoot . '/notices'; + + $file = File::at($noticesRoot . '/original.md'); + if ($file->isNotFound()) { return ''; } - list($link, $platform) = explode(' ', $originalLink, 2); - $originalLink = HtmlElement::a($platform) - ->props("href {$link}", 'itemprop sameAs') - ->build(); + $original = $frontMatter->original(); + list($href, $platform) = explode(' ', $original, 2); - $markdown = str_replace( - '{{platform link}}', - $originalLink, - $copyContent - ); + $body = Markdown::for($file)->body(); + + $matches = []; + $search = '/{!!platformlink!!}/'; + if (! preg_match($search, $body, $matches)) { + return ''; + } + + $body = preg_replace($search, "[{$platform}]({$href})", $body); + if ($body === null) { + return ''; + } - return $markdown; + return Markdown::markdownConverter()->convert($body); } } diff --git a/src/Pages/DefaultTemplate.php b/src/Pages/DefaultTemplate.php deleted file mode 100644 index 2342d8e8..00000000 --- a/src/Pages/DefaultTemplate.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ - public function headers(): array - { - $headers = []; - $headers['Content-Type'] = $this->mimeType; - return $headers; - } - - public function body(): string - { - return Document::create( - $this->pageTitle() - )->head( - ...HeadElements::create($this->contentRoot) - )->body( - Element::a('menu')->props('href #main-nav', 'id content-top'), - Element::article( - $this->body - )->props('typeof BlogPosting', 'vocab https://schema.org/'), - Element::a('top')->props('href #content-top', 'id go-to-top'), - Navigation::create($this->contentRoot)->build(), - Footer::create() - )->build(); - } - - private function pageTitle(): string - { - $titles = []; - foreach ($this->folderStack as $file) { - $fileContent = $file->with( - $file->path(full: false), - 'content.md' - ); - $titles[] = Markdown::init($fileContent) - ->frontMatter()->title(); - } - return implode(' | ', $titles); - } -} diff --git a/src/ServerGlobals.php b/src/ServerGlobals.php new file mode 100644 index 00000000..9b4ae757 --- /dev/null +++ b/src/ServerGlobals.php @@ -0,0 +1,50 @@ +appEnv() !== $value; + } + + public function isMissingAppEnv(): bool + { + return ! $this->hasAppEnv(); + } + + private function appEnv(): string + { + if ($this->hasAppEnv()) { + $globals = $this->globals(); + return strval($globals['APP_ENV']); + } + return ''; + } + + private function hasAppEnv(): bool + { + return array_key_exists('APP_ENV', $this->globals()); + } + + /** + * @return array + */ + private function globals(): array + { + return $_SERVER; + } +} diff --git a/src/SiteDynamic/Emitter.php b/src/SiteDynamic/Emitter.php index ea874348..b6c46249 100644 --- a/src/SiteDynamic/Emitter.php +++ b/src/SiteDynamic/Emitter.php @@ -4,181 +4,146 @@ namespace JoshBruce\Site\SiteDynamic; -use Nyholm\Psr7\Factory\Psr17Factory as PsrFactory; -use Nyholm\Psr7\Response as PsrResponse; use Laminas\HttpHandlerRunner\Emitter\SapiStreamEmitter as PsrEmitter; -use Eightfold\HTMLBuilder\Document; -use Eightfold\Markdown\Markdown as MarkdownConverter; - -use JoshBruce\Site\SiteDynamic\Server; -use JoshBruce\Site\FileSystem; -use JoshBruce\Site\Content\Markdown; +use JoshBruce\Site\HttpResponse; class Emitter { - /** - * @param array $headers [description] - */ - public static function emitWithResponse( - int $status, - array $headers, - string $body = '' - ): void { - $factory = new PsrFactory(); - $stream = $factory->createStream($body); - $response = new PsrResponse($status, $headers, $stream); - self::emit($response); - } - - /** - * @param array $headers [description] - */ - public static function emitWithResponseFile( - int $status, - array $headers, - string $file - ): void { - $factory = new PsrFactory(); - $stream = $factory->createStreamFromFile($file); - $response = new PsrResponse($status, $headers, $stream); - self::emit($response); - } - - public static function emit(PsrResponse $response): void + public static function emit(HttpResponse $response): void { $emitter = new PsrEmitter(); - $emitter->emit($response); - } - - public static function emitFile(string $mimeType, string $filePath): void - { - self::emitWithResponseFile( - 200, - [ - 'Cache-Control' => ['max-age=2592000'], - 'Content-Type' => $mimeType - ], - $filePath - ); - } - - public static function emitInteralServerErrorResponse( - MarkdownConverter $markdownConverter, - string $projectRoot - ): void { - $file = FileSystem::init( - $projectRoot, - '/setup-errors', - '500.md' - ); - $markdown = Markdown::init($file); - - self::emitWithResponse( - 500, - [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ], - Document::create( - $markdown->frontMatter()->title() - )->body( - $markdownConverter->convert($markdown->markdown()) - )->build() - ); - } - - public static function emitUnsupportedMethodResponse( - MarkdownConverter $markdownConverter, - string $projectRoot, - Server $server - ): void { - $file = FileSystem::init( - $projectRoot, - '/setup-errors', - '405.md' - ); - $markdown = Markdown::init($file); - - self::emitWithResponse( - 405, - [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ], - 'Allow' => $server->supportedMethods() - ], - Document::create( - $markdown->frontMatter()->title() - )->body( - $markdownConverter->convert($markdown->markdown()) - )->build() - ); - } - - public static function emitBadContentResponse( - MarkdownConverter $markdownConverter, - string $projectRoot - ): void { - $file = FileSystem::init( - $projectRoot, - '/setup-errors', - '500_2.md' - ); - $markdown = Markdown::init($file); - - self::emitWithResponse( - 502, - [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ], - Document::create( - $markdown->frontMatter()->title() - )->body( - $markdownConverter->convert($markdown->markdown()) - )->build() - ); - } - - public static function emitNotFoundResponse( - MarkdownConverter $markdownConverter, - FileSystem $file - ): void { - $markdown = Markdown::init($file); - - self::emitWithResponse( - 404, - [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ], - Document::create( - $markdown->frontMatter()->title() - )->body( - $markdownConverter->convert($markdown->markdown()) - )->build() - ); - } - - public static function emitRedirectionResponse(string $location): void - { - self::emitWithResponse( - 301, - [ - 'Location' => $location, - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ] - ); + $emitter->emit($response->psrResponse()); } +// +// public static function emitFile(string $mimeType, string $filePath): void +// { +// self::emitWithResponseFile( +// 200, +// [ +// 'Cache-Control' => ['max-age=2592000'], +// 'Content-Type' => $mimeType +// ], +// $filePath +// ); +// } +// +// public static function emitInteralServerErrorResponse( +// MarkdownConverter $markdownConverter, +// string $projectRoot +// ): void { +// $file = FileSystem::init( +// $projectRoot, +// '/setup-errors', +// '500.md' +// ); +// $markdown = Markdown::init($file); +// +// self::emitWithResponse( +// 500, +// [ +// 'Cache-Control' => [ +// 'no-cache', +// 'must-revalidate' +// ] +// ], +// Document::create( +// $markdown->frontMatter()->title() +// )->body( +// $markdownConverter->convert($markdown->markdown()) +// )->build() +// ); +// } +// +// public static function emitUnsupportedMethodResponse( +// MarkdownConverter $markdownConverter, +// string $projectRoot, +// Server $server +// ): void { +// $file = FileSystem::init( +// $projectRoot, +// '/setup-errors', +// '405.md' +// ); +// $markdown = Markdown::init($file); +// +// self::emitWithResponse( +// 405, +// [ +// 'Cache-Control' => [ +// 'no-cache', +// 'must-revalidate' +// ], +// 'Allow' => $server->supportedMethods() +// ], +// Document::create( +// $markdown->frontMatter()->title() +// )->body( +// $markdownConverter->convert($markdown->markdown()) +// )->build() +// ); +// } +// +// public static function emitBadContentResponse( +// MarkdownConverter $markdownConverter, +// string $projectRoot +// ): void { +// $file = FileSystem::init( +// $projectRoot, +// '/setup-errors', +// '500_2.md' +// ); +// $markdown = Markdown::init($file); +// +// self::emitWithResponse( +// 502, +// [ +// 'Cache-Control' => [ +// 'no-cache', +// 'must-revalidate' +// ] +// ], +// Document::create( +// $markdown->frontMatter()->title() +// )->body( +// $markdownConverter->convert($markdown->markdown()) +// )->build() +// ); +// } +// +// public static function emitNotFoundResponse( +// MarkdownConverter $markdownConverter, +// FileSystem $file +// ): void { +// $markdown = Markdown::init($file); +// +// self::emitWithResponse( +// 404, +// [ +// 'Cache-Control' => [ +// 'no-cache', +// 'must-revalidate' +// ] +// ], +// Document::create( +// $markdown->frontMatter()->title() +// )->body( +// $markdownConverter->convert($markdown->markdown()) +// )->build() +// ); +// } +// +// public static function emitRedirectionResponse(string $location): void +// { +// self::emitWithResponse( +// 301, +// [ +// 'Location' => $location, +// 'Cache-Control' => [ +// 'no-cache', +// 'must-revalidate' +// ] +// ] +// ); +// } } diff --git a/src/SiteDynamic/Server.php b/src/SiteDynamic/Server.php deleted file mode 100644 index b46363e9..00000000 --- a/src/SiteDynamic/Server.php +++ /dev/null @@ -1,150 +0,0 @@ - $serverGlobals - */ - public static function init( - array $serverGlobals, - string $projectRoot - ): Server { - return new Server( - serverGlobals: $serverGlobals, - projectRoot: $projectRoot - ); - } - - /** - * @param array $serverGlobals - */ - public function __construct( - private array $serverGlobals, - private string $projectRoot - ) { - } - - public function isMissingRequiredValues(): bool - { - $required = [ - 'APP_ENV', - 'CONTENT_UP', - 'CONTENT_FOLDER', - 'REQUEST_SCHEME', - 'HTTP_HOST', - 'REQUEST_URI' - ]; - - foreach ($required as $key) { - if (! array_key_exists($key, $this->serverGlobals)) { - return true; - } - - if ($key === 'REQUEST_URI') { - $uri = $this->serverGlobals['REQUEST_URI']; - $parts = explode('?', $uri); - $this->serverGlobals['REQUEST_URI'] = array_shift($parts); - } - } - - if ($this->serverGlobals['APP_ENV'] !== 'production') { - $erroHandler = new Run(); - $erroHandler->pushHandler(new PrettyPageHandler()); - $erroHandler->register(); - } - - return false; - } - - public function isRequestingUnsupportedMethod(): bool - { - $requestMethod = strtoupper($this->serverGlobals['REQUEST_METHOD']); - return ! in_array($requestMethod, $this->supportedMethods()); - } - - public function isRequestingFile(): bool - { - return strlen($this->requestFileName()) > 0; - } - - /** - * @return string[] - */ - public function supportedMethods(): array - { - return ['GET']; - } - - - public function contentRoot(): string - { - $projectParts = explode('/', $this->projectRoot); - $contentUp = intval($this->serverGlobals['CONTENT_UP']); - $contentFolder = strval($this->serverGlobals['CONTENT_FOLDER']); - - if (is_int($contentUp) and $contentUp > 0) { - $projectParts = array_slice($projectParts, 0, -1 * $contentUp); - } - - $contentParts = explode('/', $contentFolder); // allow for subfolders - $contentParts = array_filter($contentParts); // remove empty value - $contentParts = array_merge($projectParts, $contentParts); - - if ( - $this->isRequestingFile() and - in_array($this->requestFileName(), $contentParts) - ) { - array_pop($contentParts); - } - - return implode('/', $contentParts); - } - - public function requestFileName(): string - { - $path = $this->requestUri(); - $parts = explode('/', $path); - - $possibleFileName = array_pop($parts); - if ( - $possibleFileName !== null and - ! str_starts_with($possibleFileName, '.') and - $fileNameParts = array_filter(explode('.', $possibleFileName)) and - count($fileNameParts) > 1 - ) { - return $possibleFileName; - } - return ''; - } - - public function requestUriWithoutFileName(): string - { - if ($this->isRequestingFile()) { - $potential = '/' . $this->requestFileName(); - return str_replace($potential, '', $this->requestUri()); - } - return $this->requestUri(); - } - - public function domain(): string - { - $scheme = $this->serverGlobals['REQUEST_SCHEME']; - $serverName = $this->serverGlobals['HTTP_HOST']; - return $scheme . '://' . $serverName; - } - - private function requestUri(): string - { - if ($this->serverGlobals['REQUEST_URI'] === '/') { - return ''; - } - return $this->serverGlobals['REQUEST_URI']; - } -} diff --git a/src/SiteStatic/Generator.php b/src/SiteStatic/Generator.php index 81239f00..95687883 100644 --- a/src/SiteStatic/Generator.php +++ b/src/SiteStatic/Generator.php @@ -4,57 +4,64 @@ namespace JoshBruce\Site\SiteStatic; +use SplFileInfo; + +use Dotenv\Dotenv; + use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Finder\Finder; use League\Flysystem\Local\LocalFilesystemAdapter; use League\Flysystem\Filesystem as LeagueFilesystem; -use Eightfold\Markdown\Markdown as MarkdownConverter; - use JoshBruce\Site\FileSystem; -use JoshBruce\Site\Content\Markdown; -use JoshBruce\Site\Pages\DefaultTemplate; +use JoshBruce\Site\HttpResponse; +use JoshBruce\Site\HttpRequest; class Generator { private bool $isNotTesting = false; + private string $contentRoot = ''; + private LeagueFilesystem $leagueFileSystem; public static function init( OutputInterface $output, - string $contentRoot, - string $destination + string $destination = '' ): Generator { - return new Generator($output, $contentRoot, $destination); + return new Generator($output, $destination); } - public function __construct( + private function __construct( private OutputInterface $output, - private string $contentRoot, - private string $destination + private string $destination = '' ) { + $projectRoot = FileSystem::projectRoot(); + + Dotenv::createImmutable($projectRoot)->load(); + if (isset($_SERVER['APP_ENV'])) { $this->isNotTesting = $_SERVER['APP_ENV'] !== 'testing'; } + + $this->contentRoot = FileSystem::contentRoot() . '/public'; + + if (strlen($destination) === 0) { + $this->destination = $projectRoot . '/site-static-html/public'; + } } public function compile(): bool { $this->compilingDidStartMessage($this->contentRoot, $this->destination); - $finder = new Finder(); - $finder = $finder->ignoreVCS(false) - ->ignoreUnreadableDirs() - ->ignoreDotFiles(false) - ->in($this->contentRoot); + $finder = $this->finder(); + foreach ($finder as $found) { $path = (string) $found; - if (strpos($path, '_') > 0 or $found->isDir()) { - continue; - } if (str_contains($path, '.md')) { $this->compileContentFileFor($path); @@ -64,44 +71,46 @@ public function compile(): bool } } - - if ($this->isNotTesting) { - $end = hrtime(true); - } return true; } - private function compileContentFileFor(string $contentPath): void { - $contentRoot = $this->contentRoot; - $path = str_replace($contentRoot, '', $contentPath); + $destinationPath = $this->contentDestinationPathFor($contentPath); - $parts = explode('/', $path); + $relativePath = $this->relativePath($contentPath); + $parts = explode('/', $relativePath); + $parts = array_slice($parts, 0, -1); + $requestUri = implode('/', $parts); - $fileName = array_pop($parts); + $_SERVER['REQUEST_URI'] = $requestUri; + if (str_contains($destinationPath, '/error-404.html')) { + $_SERVER['REQUEST_URI'] = '/low/probability/of/ex/is/ting'; - $folderPath = implode('/', $parts); - if (strlen($folderPath) === 0) { - $folderPath = '/'; - } + } elseif (str_contains($destinationPath, '/error-405.html')) { + $_SERVER['REQUEST_METHOD'] = 'DELETE'; - $destinationPath = $this->contentDestinationPathFor($contentPath); + } elseif (strlen($requestUri) === 0) { + $_SERVER['REQUEST_URI'] = '/'; - $file = FileSystem::init($contentRoot, $folderPath, $fileName); + } - $body = Markdown::init($file)->convert(); + // $_SERVER['REQUEST_URI'] = (strlen($requestUri) === 0) + // ? '/' + // : $requestUri; - $content = DefaultTemplate::create( - $body, - $file->mimeType(), - $file->folderStack(), - $contentRoot - )->body(); + $html = HttpResponse::from(request: HttpRequest::fromGlobals())->body(); - $this->leagueFileSystem()->write($destinationPath, $content); + $this->leagueFileSystem()->write($destinationPath, $html); - $this->sourceFileConvertedMessage($contentPath, $destinationPath); + // $this->sourceFileConvertedMessage($contentPath, $destinationPath); + } + + private function copyFileFor(string $path): void + { + $destinationPath = $this->fileDestinationPathFor($path); + $this->leagueFileSystem()->copy($path, $destinationPath); + // $this->sourceFileCopiedMessage($path, $destinationPath); } private function contentDestinationPathFor(string $path): string @@ -115,17 +124,37 @@ private function contentDestinationPathFor(string $path): string private function fileDestinationPathFor(string $path): string { - $contentRoot = $this->contentRoot; - $relativePath = str_replace($contentRoot, '', $path); - + $relativePath = $this->relativePath($path); return $this->destination . $relativePath; } - private function copyFileFor(string $path): void + private function finder(): Finder { - $destinationPath = $this->fileDestinationPathFor($path); - $this->leagueFileSystem()->copy($path, $destinationPath); - $this->sourceFileCopiedMessage($path, $destinationPath); + $finder = new Finder(); + return $finder->ignoreVCS(false) + ->ignoreUnreadableDirs() + ->ignoreDotFiles(false) + ->ignoreVCSIgnored(true) + ->files() + ->filter(fn($f) => $this->isPublished($f)) + ->in($this->contentRoot); + } + + private function isPublished(SplFileInfo $finderFile): bool + { + return ! $this->isDraft($finderFile); + } + + private function isDraft(SplFileInfo $finderFile): bool + { + $filePath = (string) $finderFile; + $relativePath = $this->relativePath($filePath); + return str_contains($relativePath, '_'); + } + + private function relativePath(string $path): string + { + return str_replace($this->contentRoot, '', $path); } /** @@ -165,29 +194,33 @@ private function compilingDidStartMessage( } } - private function sourceFileConvertedMessage( + private function sourceFileCopiedMessage( string $contentPath, string $destinationPath ): void { - $this->output()->writeln(<<isNotTesting) { + $this->output()->writeln(<<output()->writeln(<<isNotTesting) { + $this->output()->writeln(<<setName('compile') - ->setVersion('0.0.1') - ->addArgument('source', InputArgument::REQUIRED, '/path/to/cotent/source') - ->addArgument('destination', InputArgument::REQUIRED, '/path/to/destination') + ->setVersion('0.2.1') + ->addArgument('destination', InputArgument::OPTIONAL, '/path/to/destination') ->setCode(function (InputInterface $input, OutputInterface $output) { - $source = $input->getArgument('source'); - if (! file_exists($source)) { - $output->writeln('The source directory could not be found.'); - return Command::FAILURE; - } - - if (! is_dir($source)) { - $output->writeln('The source should be a directory.'); - return Command::FAILURE; - } $destination = $input->getArgument('destination'); - if (! file_exists($destination)) { - $output->writeln('The destination directory could not be found.'); - return Command::FAILURE; - } - - if (! is_dir($destination)) { - $output->writeln('The destination should be a directory.'); - return Command::FAILURE; + if ($destination === null) { + $destination = ''; } $start = hrtime(true); - $generator = Generator::init($output, $source, $destination); + $generator = Generator::init($output, $destination); if ($generator->compile()) { - $end = hrtime(true); $elapsed = $end - $start; $ms = round($elapsed/1e+6); diff --git a/tests/ContentTest.php b/tests/ContentTest.php index d1ea3403..34c1fff4 100644 --- a/tests/ContentTest.php +++ b/tests/ContentTest.php @@ -1,58 +1,18 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - $this->contentRoot = $this->projectRoot . '/tests/test-content'; - - $this->fileSystem = FileSystem::init( - contentRoot: $this->contentRoot, - folderPath: '/tests/test-content' - ); - - $this->file = $this->fileSystem->with( - folderPath: '', - fileName: 'content.md' - ); -}); - -it('can return front matter', function() { - expect( - Markdown::init($this->file)->frontMatter() - )->toBeInstanceOf( - FrontMatter::class - ); -}); - - - -// it('has correct mimetypes', function() { -// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/ -// // MIME_types#textjavascript +// +// use JoshBruce\Site\Content\Markdown; +// +// use JoshBruce\Site\FileSystem; +// +// use JoshBruce\Site\Content\FrontMatter; +// +// it('can return front matter', function() { // expect( -// $this->baseContent->for(path: '/.assets/javascript.js')->mimetype() -// )->toBe( -// 'text/javascript' +// Markdown::init( +// FileSystem::public('content.md') +// )->frontMatter() +// )->toBeInstanceOf( +// FrontMatter::class // ); - -// expect( -// $this->baseContent->for(path: '/.assets/main.css')->mimetype() -// )->toBe( -// 'text/css' -// ); - -// expect( -// $this->baseContent->for(path: '/content.md')->mimetype() -// )->toBe( -// 'text/html' -// ); - -// })->group('content'); +// }); +// diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php new file mode 100644 index 00000000..e8bf0b2c --- /dev/null +++ b/tests/EnvironmentTest.php @@ -0,0 +1,20 @@ +projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +// }); +// +// it('has expected .env variables', function() { +// Dotenv\Dotenv::createImmutable($this->projectRoot)->load(); +// +// expect( +// $_SERVER['APP_ENV'] +// )->toBe( +// 'local', +// '.env file with APP_ENV set to local could not be found.' +// ); +// }); diff --git a/tests/FileSystemTest.php b/tests/FileSystemTest.php index b50fe481..3cb762b6 100644 --- a/tests/FileSystemTest.php +++ b/tests/FileSystemTest.php @@ -1,159 +1,181 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - serverGlobals(); - - $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; -}); - -it('can return folder tree', function() { - $this->assertEquals( - FileSystem::init($this->contentRoot) - ->with(folderPath: '/sub-folder')->folderStack(), - [ - FileSystem::init($this->contentRoot)->with(folderPath: '/sub-folder'), - FileSystem::init($this->contentRoot)->with(folderPath: '') - ] - ); - - $this->assertEquals( - FileSystem::init($this->contentRoot) - ->with(folderPath: '/sub-folder') - ->folderStack(fileName: 'content.md'), - [ - FileSystem::init($this->contentRoot)->with( - folderPath: '/sub-folder', - fileName: 'content.md' - ), - FileSystem::init($this->contentRoot) - ->with(folderPath: '', fileName: 'content.md') - ] - ); -})->group('filesystem'); - -it('can determine if path is content root', function() { - expect( - FileSystem::init($this->contentRoot)->isRoot() - )->toBeTrue(); - - expect( - FileSystem::init($this->contentRoot, '/not-there')->isRoot() - )->toBeFalse(); - - expect( - FileSystem::init($this->contentRoot)->isNotRoot() - )->toBeFalse(); - - expect( - FileSystem::init($this->contentRoot, '/not-there')->isNotRoot() - )->toBeTrue(); -})->group('filesystem'); - -it('can determine if path exists', function() { - expect( - FileSystem::init($this->contentRoot)->found() - )->toBeTrue(); - - expect( - FileSystem::init($this->contentRoot . '/not-there')->found() - )->toBeFalse(); - - expect( - FileSystem::init($this->contentRoot)->notFound() - )->toBeFalse(); - - expect( - FileSystem::init($this->contentRoot . '/not-there')->notFound() - )->toBeTrue(); -})->group('filesystem'); - -it('can detect whether the root folder exists', function() { - expect( - FileSystem::init($this->contentRoot)->rootFolderIsMissing() - )->toBeFalse(); - - expect( - FileSystem::init($this->contentRoot . '/not-there')->rootFolderIsMissing() - )->toBeTrue(); -})->group('filesystem'); - -it('has correct file path', function() { - expect( - FileSystem::init($this->contentRoot)->with( - folderPath: '/assets/js', - fileName: 'javascript.js' - )->path() - )->toBe( - __DIR__ . '/test-content/content/assets/js/javascript.js' - ); - - expect( - FileSystem::init($this->contentRoot)->navigation('main.md')->path() - )->toBe( - __DIR__ . '/test-content/navigation/main.md' - ); - - expect( - FileSystem::init($this->contentRoot)->messages('original.md')->path() - )->toBe( - __DIR__ . '/test-content/messages/original.md' - ); - - expect( - FileSystem::init($this->contentRoot)->messages()->path() - )->toBe( - __DIR__ . '/test-content/messages' - ); - - expect( - FileSystem::init($this->contentRoot, '/sub-folder', 'content.md') - ->path(false) - )->toBe( - '/sub-folder/content.md' - ); -})->group('filesystem'); - -it('has the correct content root', function() { - expect( - FileSystem::init($this->contentRoot)->contentRoot() - )->toBe( - __DIR__ . '/test-content/content' - ); -})->group('filesystem'); - -it('has correct mimetypes', function() { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/ - // MIME_types#textjavascript - expect( - FileSystem::init($this->contentRoot)->with( - folderPath: '/assets/js', - fileName: 'javascript.js' - )->mimetype() - )->toBe( - 'text/javascript' - ); - - expect( - FileSystem::init($this->contentRoot)->with( - folderPath: '/assets/css', - fileName: 'main.css' - )->mimetype() - )->toBe( - 'text/css' - ); - - expect( - FileSystem::init($this->contentRoot)->with( - folderPath: '/', - fileName: 'content.md' - )->mimetype() - )->toBe( - 'text/html' - ); -})->group('filesystem'); +// +// use JoshBruce\Site\FileSystem; +// +// use JoshBruce\Site\File; +// +// beforeEach(function() { +// // This somewhat unreadable one-liner basically creates a fully qualified +// // path to the root of the project, without using relative syntax +// $this->projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +// +// serverGlobals(); +// +// $this->contentRoot = $this->projectRoot . '/content'; +// }); +// +// test('content folder does exist', function() { +// expect( +// FileSystem::contentFolderIsMissing() +// )->toBeFalse(); +// })->group('filesystem'); +// +// it('can initialize folders', function() { +// expect( +// FileSystem::public()->path() +// )->toBeString()->toBe( +// "{$this->contentRoot}/public" +// ); +// +// expect( +// FileSystem::navigation()->path() +// )->toBeString()->toBe( +// "{$this->contentRoot}/navigation" +// ); +// })->group('filesystem'); +// +// // it('can return folder tree', function() { +// // $this->assertEquals( +// // FileSystem::init( +// // $this->contentRoot, +// // 'public', +// // 'finances', +// // 'investment-policy' +// // )->folderStack(), +// // [ +// // FileSystem::init( +// // $this->contentRoot, +// // 'public', +// // 'finances', +// // 'investment-policy' +// // ), +// // FileSystem::init( +// // $this->contentRoot, +// // 'public', +// // 'finances' +// // ), +// // FileSystem::init( +// // $this->contentRoot, +// // 'public' +// // ), +// // FileSystem::init( +// // $this->contentRoot +// // ) +// // ] +// // ); +// // +// // $this->assertEquals( +// // FileSystem::init($this->contentRoot) +// // ->with(folderPath: '/sub-folder') +// // ->folderStack(fileName: 'content.md'), +// // [ +// // FileSystem::init($this->contentRoot)->with( +// // folderPath: '/sub-folder', +// // fileName: 'content.md' +// // ), +// // FileSystem::init($this->contentRoot) +// // ->with(folderPath: '', fileName: 'content.md') +// // ] +// // ); +// // })->group('filesystem', 'focus'); +// // +// // it('can determine if path is content root', function() { +// // expect( +// // FileSystem::init($this->contentRoot)->isRoot() +// // )->toBeTrue(); +// // +// // expect( +// // FileSystem::init($this->contentRoot, '/not-there')->isRoot() +// // )->toBeFalse(); +// // +// // expect( +// // FileSystem::init($this->contentRoot)->isNotRoot() +// // )->toBeFalse(); +// // +// // expect( +// // FileSystem::init($this->contentRoot, '/not-there')->isNotRoot() +// // )->toBeTrue(); +// // })->group('filesystem'); +// +// // it('can determine if path exists', function() { +// // expect( +// // FileSystem::init($this->contentRoot)->found() +// // )->toBeTrue(); +// // +// // expect( +// // FileSystem::init($this->contentRoot . '/not-there')->found() +// // )->toBeFalse(); +// // +// // expect( +// // FileSystem::init($this->contentRoot)->notFound() +// // )->toBeFalse(); +// // +// // expect( +// // FileSystem::init($this->contentRoot . '/not-there')->notFound() +// // )->toBeTrue(); +// // })->group('filesystem'); +// // +// // it('can detect whether the root folder exists', function() { +// // expect( +// // FileSystem::init($this->contentRoot)->rootFolderIsMissing() +// // )->toBeFalse(); +// // +// // expect( +// // FileSystem::init($this->contentRoot . '/not-there')->rootFolderIsMissing() +// // )->toBeTrue(); +// // })->group('filesystem'); +// // +// // it('has correct file path', function() { +// // expect( +// // FileSystem::init($this->contentRoot, 'css', 'main.min.css')->path() +// // )->toBe( +// // "{$this->contentRoot}/css/main.min.css" +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot)->navigation('main.md')->path() +// // )->toBe( +// // "{$this->contentRoot}/navigation/main.md" +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot)->messages('original.md')->path() +// // )->toBe( +// // "{$this->contentRoot}/messages/original.md" +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot)->messages()->path() +// // )->toBe( +// // "{$this->contentRoot}/messages" +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot, 'public', 'legal', 'content.md') +// // ->path(false) +// // )->toBe( +// // '/public/legal/content.md' +// // ); +// // })->group('filesystem'); +// // +// // it('has correct mimetypes', function() { +// // // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/ +// // // MIME_types#textjavascript +// // expect( +// // FileSystem::init($this->contentRoot, 'gulpfile.js')->mimetype() +// // )->toBe( +// // 'text/javascript' +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot, 'css', 'main.min.css')->mimetype() +// // )->toBe( +// // 'text/css' +// // ); +// // +// // expect( +// // FileSystem::init($this->contentRoot, 'public', 'content.md')->mimetype() +// // )->toBe( +// // 'text/html' +// // ); +// // })->group('filesystem'); diff --git a/tests/HttpTest.php b/tests/HttpTest.php new file mode 100644 index 00000000..ed2cb881 --- /dev/null +++ b/tests/HttpTest.php @@ -0,0 +1,189 @@ +headers() + )->toBe( + ['Content-Type' => 'text/html'] + ); + + serverGlobals('/assets/css/main.min.css'); + + expect( + HttpResponse::from( + request: HttpRequest::fromGlobals() + )->headers() + )->toBe( + ['Content-Type' => 'text/css'] + ); +})->group('response'); + +test('expected titles', function() { + serverGlobals(); + + $body = HttpResponse::from( + request: HttpRequest::fromGlobals() + )->body(); + + expect( + str_contains($body, "Josh Bruce's personal site") + )->toBeTrue(); + + serverGlobals('/finances'); + + $body = HttpResponse::from( + request: HttpRequest::fromGlobals() + )->body(); + + expect( + str_contains( + $body, + "Finances | Josh Bruce's personal site" + ) + )->toBeTrue(); + + serverGlobals('/something-missing'); + + $body = HttpResponse::from( + request: HttpRequest::fromGlobals() + )->body(); + + expect( + str_contains( + $body, + "Page not found" + ) + )->toBeTrue(); + +// unset($_SERVER['APP_ENV']); +// +// expect( +// HttpResponse::from( +// request: HttpRequest::fromGlobals() +// )->statusCode() +// )->toBeInt()->toBe( +// 500 +// ); +// +// serverGlobals(); +// +// $_SERVER['REQUEST_METHOD'] = 'post'; +// +// expect( +// HttpResponse::from( +// request: HttpRequest::fromGlobals() +// )->statusCode() +// )->toBeInt()->toBe( +// 405 +// ); +// +// serverGlobals('/not-valid'); +// +// expect( +// HttpResponse::from( +// request: HttpRequest::fromGlobals() +// )->statusCode() +// )->toBeInt()->toBe( +// 404 +// ); +})->group('response'); + +test('expected status codes', function() { + serverGlobals(); + + expect( + HttpResponse::from( + request: HttpRequest::fromGlobals() + )->statusCode() + )->toBeInt()->toBe( + 200 + ); + + unset($_SERVER['APP_ENV']); + + expect( + HttpResponse::from( + request: HttpRequest::fromGlobals() + )->statusCode() + )->toBeInt()->toBe( + 500 + ); + + serverGlobals(); + + $_SERVER['REQUEST_METHOD'] = 'post'; + + expect( + HttpResponse::from( + request: HttpRequest::fromGlobals() + )->statusCode() + )->toBeInt()->toBe( + 405 + ); + + serverGlobals('/not-valid'); + + expect( + HttpResponse::from( + request: HttpRequest::fromGlobals() + )->statusCode() + )->toBeInt()->toBe( + 404 + ); +})->group('response'); +// +// test('can check request is valid', function() { +// serverGlobals(); +// +// expect( +// HttpRequest::init()->isMissingRequiredValues() +// )->toBeFalse(); +// +// expect( +// HttpRequest::init()->isUnsupportedMethod() +// )->toBeFalse(); +// +// expect( +// HttpRequest::init()->isNotFound() +// )->toBeFalse(); +// +// serverGlobals('/not-valid'); +// unset($_SERVER['APP_ENV']); +// $_SERVER['REQUEST_METHOD'] = 'post'; +// +// expect( +// HttpRequest::init()->isMissingRequiredValues() +// )->toBeTrue(); +// +// expect( +// HttpRequest::init()->isUnsupportedMethod() +// )->toBeTrue(); +// +// expect( +// HttpRequest::init()->isNotFound() +// )->toBeTrue(); +// })->group('request'); +// +// it('uses server globals', function() { +// serverGlobals(); +// +// expect( +// ServerGlobals::init()->hasAppEnv() +// )->toBeTrue(); +// +// unset($_SERVER['APP_ENV']); +// +// expect( +// ServerGlobals::init()->hasAppEnv() +// )->toBeFalse(); +// })->group('globals'); diff --git a/tests/IndexTest.php b/tests/IndexTest.php new file mode 100644 index 00000000..32a23b31 --- /dev/null +++ b/tests/IndexTest.php @@ -0,0 +1,29 @@ +toBeTrue(); + + $contents = file_get_contents($path); + + preg_match_all('/ini_set\(.*\);/', $contents, $matches); + $matches = array_shift($matches); + + expect(count($matches))->toBeGreaterThan(0); + + foreach ($matches as $match) { + expect( + str_contains($match, '1'), + 'Okay to fail in local.' + )->toBeFalse(); + } +})->group('index', 'focus'); diff --git a/tests/PageComponentTest.php b/tests/PageComponentTest.php index 85ba46f5..d4862d27 100644 --- a/tests/PageComponentTest.php +++ b/tests/PageComponentTest.php @@ -1,74 +1,74 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - serverGlobals(); - - $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; - - $this->fileSystem = FileSystem::init($this->contentRoot); -}); - -test('navigation', function() { - expect( - Navigation::create($this->fileSystem->contentRoot())->build() - )->toBe(<< - html - ); -}); - -use JoshBruce\Site\Content\FrontMatter; - -test('heading', function() { - expect( - Heading::create( - FrontMatter::init(['title' => 'Hello, World!']) - ) - )->toBe(<< '20210101']) - ) - )->toBe(<<

Created on:

- html - ); - - expect( - DateBlock::create( - FrontMatter::init(['updated' => '20210101']) - ) - )->toBe(<<

Updated on:

- html - ); - - expect( - DateBlock::create( - FrontMatter::init([ - 'created' => '20210110', - 'updated' => '20210101' - ]) - ) - )->toBe(<<

Created on:

Updated on:

- html - ); - -})->group('components'); +// +// use JoshBruce\Site\PageComponents\DateBlock; +// use JoshBruce\Site\PageComponents\Heading; +// use JoshBruce\Site\PageComponents\Navigation; +// +// use JoshBruce\Site\FileSystem; +// +// // beforeEach(function() { +// // // This somewhat unreadable one-liner basically creates a fully qualified +// // // path to the root of the project, without using relative syntax +// // $this->projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +// // +// // serverGlobals(); +// // +// // $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; +// // +// // $this->fileSystem = FileSystem::init($this->contentRoot); +// // }); +// +// test('navigation', function() { +// expect( +// Navigation::create('main.md')->build() +// )->toBe(<< +// html +// ); +// })->group('components', 'navigation'); +// +// use JoshBruce\Site\Content\FrontMatter; +// +// test('heading', function() { +// expect( +// Heading::create( +// FrontMatter::init(['title' => 'Hello, World!']) +// ) +// )->toBe(<< '20210101']) +// ) +// )->toBe(<<

Created on:

+// html +// ); +// +// expect( +// DateBlock::create( +// FrontMatter::init(['updated' => '20210101']) +// ) +// )->toBe(<<

Updated on:

+// html +// ); +// +// expect( +// DateBlock::create( +// FrontMatter::init([ +// 'created' => '20210110', +// 'updated' => '20210101' +// ]) +// ) +// )->toBe(<<

Created on:

Updated on:

+// html +// ); +// +// })->group('components'); diff --git a/tests/Pest.php b/tests/Pest.php index 5ee7c2cb..0ac9c7af 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -40,12 +40,12 @@ function server(string $requestUri = '/'): Server function serverGlobals(string $requestUri = '/'): array { $_SERVER['APP_ENV'] = 'test'; - $_SERVER['CONTENT_UP'] = 0; - $_SERVER['CONTENT_FOLDER'] = '/tests/test-content/content'; - $_SERVER['REQUEST_SCHEME'] = 'http'; - $_SERVER['HTTP_HOST'] = 'testing.com'; + // $_SERVER['CONTENT_UP'] = 0; + // $_SERVER['CONTENT_FOLDER'] = '/tests/test-content/content'; + // $_SERVER['REQUEST_SCHEME'] = 'http'; + // $_SERVER['HTTP_HOST'] = 'testing.com'; $_SERVER['REQUEST_URI'] = $requestUri; - $_SERVER['REQUEST_METHOD'] = 'get'; + $_SERVER['REQUEST_METHOD'] = 'GET'; return $_SERVER; } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 58e947d3..d18626be 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -1,57 +1,65 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - serverGlobals(); - - $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; -}); - -it('has expected file name and content root', function() { - expect( - Server::init(serverGlobals(), $this->projectRoot)->requestFileName() - )->toBe( - '' - ); - - expect( - Server::init(serverGlobals(), $this->projectRoot)->contentRoot() - )->toBe( - __DIR__ . '/test-content/content' - ); -})->group('server'); - -it('limits request methods', function() { - expect( - Server::init(serverGlobals(), $this->projectRoot) - ->isRequestingUnsupportedMethod() - )->toBeFalse(); - - $serverGlobals = serverGlobals(); - $serverGlobals['REQUEST_METHOD'] = 'INVALID'; - - expect( - Server::init($serverGlobals, $this->projectRoot) - ->isRequestingUnsupportedMethod() - )->toBeTrue(); -})->group('server'); - -it('has required variables', function() { - expect( - Server::init(serverGlobals(), $this->projectRoot) - ->isMissingRequiredValues() - )->toBeFalse(); - - $serverGlobals = serverGlobals(); - unset($serverGlobals['CONTENT_UP']); - - expect( - Server::init($serverGlobals, $this->projectRoot) - ->isMissingRequiredValues() - )->toBeTrue(); -})->group('server'); +// +// use JoshBruce\Site\SiteDynamic\Server; +// +// beforeEach(function() { +// // This somewhat unreadable one-liner basically creates a fully qualified +// // path to the root of the project, without using relative syntax +// $this->projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); +// +// serverGlobals(); +// +// $this->contentRoot = $this->projectRoot . $_SERVER['CONTENT_FOLDER']; +// }); +// +// it('has expected project roo', function() { +// expect( +// Server::projectRoot() +// )->toBeString()->toBe( +// $this->projectRoot +// ); +// })->group('server'); +// +// it('has expected file name and content root', function() { +// expect( +// Server::init(serverGlobals(), $this->projectRoot)->requestFileName() +// )->toBe( +// '' +// ); +// +// expect( +// Server::init(serverGlobals(), $this->projectRoot)->contentRoot() +// )->toBe( +// __DIR__ . '/test-content/content' +// ); +// })->group('server'); +// +// it('limits request methods', function() { +// expect( +// Server::init(serverGlobals(), $this->projectRoot) +// ->isRequestingUnsupportedMethod() +// )->toBeFalse(); +// +// $serverGlobals = serverGlobals(); +// $serverGlobals['REQUEST_METHOD'] = 'INVALID'; +// +// expect( +// Server::init($serverGlobals, $this->projectRoot) +// ->isRequestingUnsupportedMethod() +// )->toBeTrue(); +// })->group('server'); +// +// it('has required variables', function() { +// expect( +// Server::init(serverGlobals(), $this->projectRoot) +// ->isMissingRequiredValues() +// )->toBeFalse(); +// +// $serverGlobals = serverGlobals(); +// unset($serverGlobals['CONTENT_UP']); +// +// expect( +// Server::init($serverGlobals, $this->projectRoot) +// ->isMissingRequiredValues() +// )->toBeTrue(); +// })->group('server'); diff --git a/tests/StaticGenerator/GeneratorTest.php b/tests/StaticGenerator/GeneratorTest.php index 62555f3e..1b0e8f16 100644 --- a/tests/StaticGenerator/GeneratorTest.php +++ b/tests/StaticGenerator/GeneratorTest.php @@ -1,35 +1,35 @@ projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -2)); - - $this->contentRoot = $this->projectRoot . '/tests/test-content/content'; - - $this->destination = $this->projectRoot . '/tests/compiled/content'; - - $_SERVER['APP_ENV'] = 'testing'; -}); - -it('can instantiate generator', function() { - expect( - new Generator( - new OutputInterface(), - $this->contentRoot, - $this->destination - ) - )->toBeInstanceOf( - Generator::class - ); -})->group('static'); - -it('can create output interface instance', function() { - expect( - new OutputInterface() - )->toBeInstanceOf( - OutputInterface::class - ); -})->group('static'); +// +// use JoshBruce\Site\SiteStatic\Generator; +// +// use JoshBruce\Site\Tests\StaticGenerator\OutputInterface; +// +// beforeEach(function() { +// $this->projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -2)); +// +// $this->contentRoot = $this->projectRoot . '/tests/test-content/content'; +// +// $this->destination = $this->projectRoot . '/tests/compiled/content'; +// +// $_SERVER['APP_ENV'] = 'testing'; +// }); +// +// it('can instantiate generator', function() { +// expect( +// new Generator( +// new OutputInterface(), +// $this->contentRoot, +// $this->destination +// ) +// )->toBeInstanceOf( +// Generator::class +// ); +// })->group('static'); +// +// it('can create output interface instance', function() { +// expect( +// new OutputInterface() +// )->toBeInstanceOf( +// OutputInterface::class +// ); +// })->group('static'); diff --git a/tests/test-content/content/assets/css/main.css b/tests/test-content/content/assets/css/main.css deleted file mode 100644 index 5f93228c..00000000 --- a/tests/test-content/content/assets/css/main.css +++ /dev/null @@ -1 +0,0 @@ -main.css \ No newline at end of file diff --git a/tests/test-content/content/assets/js/javascript.js b/tests/test-content/content/assets/js/javascript.js deleted file mode 100644 index bdb9f403..00000000 --- a/tests/test-content/content/assets/js/javascript.js +++ /dev/null @@ -1 +0,0 @@ -function something() {} diff --git a/tests/test-content/content/content.md b/tests/test-content/content/content.md deleted file mode 100644 index a60ca0a8..00000000 --- a/tests/test-content/content/content.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Josh Bruce's personal site ---- - -# The domain of Josh Bruce - -This content was successfully found. diff --git a/tests/test-content/content/sub-folder/content.md b/tests/test-content/content/sub-folder/content.md deleted file mode 100644 index 27bac32f..00000000 --- a/tests/test-content/content/sub-folder/content.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: sub-folder | Josh Bruce's personal site ---- - -# A sub-folder content - -This content was successfully found. diff --git a/tests/test-content/errors/404.md b/tests/test-content/errors/404.md deleted file mode 100644 index 574b0cbd..00000000 --- a/tests/test-content/errors/404.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Not found ---- - -# 404: Not found - -We still haven't found what you're looking for. diff --git a/tests/test-content/navigation/main.md b/tests/test-content/navigation/main.md deleted file mode 100644 index bb56c231..00000000 --- a/tests/test-content/navigation/main.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -navigation: [ - / home, [ - /finances Finances, - /finances/investment-policy Investment policy, - /finances/building-wealth-paycheck-to-paycheck Paycheck to paycheck - ], [ - /design-your-life Design your life, - /design-your-life/motivators Motivators - ], [ - /software-development Software development, - /software-development/why-dont-you-use Why don't you use, - /somethig-with-commas Some\ commas\ and whatnot - ] -] ----