inital commit

This commit is contained in:
Edward Shen 2023-09-04 13:22:12 -07:00
commit fece8daf3a
Signed by: edward
GPG key ID: 19182661E818369F
28 changed files with 4028 additions and 0 deletions

1
eddie.sh-deno/.gitconfig Normal file
View file

@ -0,0 +1 @@
_site/

3
eddie.sh-deno/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"deno.enable": true
}

51
eddie.sh-deno/_config.ts Normal file
View file

@ -0,0 +1,51 @@
import lume from "lume/mod.ts";
import jsx from "lume/plugins/jsx.ts";
import katex from "lume/plugins/katex.ts";
import mdx from "lume/plugins/mdx.ts";
import prism from "lume/plugins/prism.ts";
import slugify_urls from "lume/plugins/slugify_urls.ts";
import tailwindcss from "lume/plugins/tailwindcss.ts";
import postcss from "lume/plugins/postcss.ts";
import metas from "lume/plugins/metas.ts";
import { RenderRule } from "npm:@types/markdown-it/lib/renderer.d.ts";
import MarkdownIt from "npm:@types/markdown-it";
import esbuild from "lume/plugins/esbuild.ts";
import sourceMaps from "lume/plugins/source_maps.ts";
const site = lume({
src: "./src",
dest: "./_site",
location: new URL("https://eddie.sh"),
});
site.use(katex());
site.use(jsx());
site.use(mdx());
site.use(prism());
site.use(slugify_urls());
site.use(tailwindcss());
site.use(postcss());
site.use(metas());
site.use(esbuild());
site.use(sourceMaps());
let original_link_open_renderer: RenderRule;
site.hooks.markdownIt((md: MarkdownIt) => {
const proxy: RenderRule = (tokens, idx, options, _, self) => self.renderToken(tokens, idx, options);
original_link_open_renderer = md.renderer.rules.link_open || proxy;
});
const link_open_rule_add_noopener_noreferrer: RenderRule = (tokens, idx, options, env, self) => {
for (const token of tokens) {
if (token.type === "link_open") {
token.attrJoin("rel", "noopener");
token.attrJoin("rel", "noreferrer");
}
}
return original_link_open_renderer(tokens, idx, options, env, self);
};
site.hooks.addMarkdownItRule("link_open", link_open_rule_add_noopener_noreferrer);
export default site;

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html><head>
<title>Edward Shen</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Code+Latin&amp;display=swap" rel="stylesheet">
<!-- <style src="./styles.css"></style> -->
<!-- <style>
html {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
}
body {
font-family: 'M PLUS Code Latin', sans-serif;
background-color: #000;
max-width: 84ch;
width: 84ch;
padding: 0 2ch;
color: #ccc;
overflow: hidden;
}
a:visited {
color: #999;
}
a {
color: #ccc;
}
ul {
list-style-type: "> ";
}
</style> -->
<meta property="og:type" content="website">
<meta property="og:url" content="http://localhost:3000/">
<meta name="twitter:card" content="summary">
</head>
<body><h1>Edward Shen</h1>
<p>Rust; Rhythm games; Food; Homebrewing; Security; Scalability; Free software.</p>
<p><a href="https://github.com/edward-shen" rel="noopener noreferrer">Github</a></p>
<br>
<p>Interesting projects:</p>
<ul>
<li><a href="https://github.com/edward-shen/omegaupload" rel="noopener noreferrer">OmegaUpload</a></li>
<li><a href="https://github.com/edward-shen/bunbun" rel="noopener noreferrer">bunbun</a></li>
<li><a href="https://github.com/edward-shen/shlink" rel="noopener noreferrer">Shlink browser extension</a></li>
<li><a href="https://github.com/edward-shen/ayaya" rel="noopener noreferrer">ayaya</a></li>
</ul>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bank of America Preferred Rewards Probably Suck</title>
<link rel="icon" href="data:,">
<meta property="og:type" content="website">
<meta property="og:url" content="http://localhost:3000/oneshots/bofa-rewards/">
<meta name="twitter:card" content="summary">
</head>
<body>
<h1>Bank of America Preferred Rewards Probably Suck</h1>
<h3>2023-04-03</h3>
<p>Bank of America (BofA) has a rewards program that is tiered based on how much of
money you have with BofA or an associated Merrill account. There are five tiers
that they offer, with (among other things) a credit card percent bonus:</p>
<table>
<thead>
<tr>
<th>Tier Name</th>
<th style="text-align:right">Minimum Balance Required (USD)</th>
<th style="text-align:right">Credit Card Bonus</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gold</td>
<td style="text-align:right">20,000</td>
<td style="text-align:right">25%</td>
</tr>
<tr>
<td>Platinum</td>
<td style="text-align:right">50,000</td>
<td style="text-align:right">50%</td>
</tr>
<tr>
<td>Platinum Honors</td>
<td style="text-align:right">100,000</td>
<td style="text-align:right">75%</td>
</tr>
<tr>
<td>Diamond</td>
<td style="text-align:right">1,000,000</td>
<td style="text-align:right">75%</td>
</tr>
<tr>
<td>Diamond Honors</td>
<td style="text-align:right">10,000,000</td>
<td style="text-align:right">75%</td>
</tr>
</tbody>
</table>
<p>The credit card percent bonus is an additional amount of points or cash back
based on how much you original were going to get. For example, a 75% percent
bonus on a 1.5 points per dollar purchase gives you an additional 1.125 points,
or an effective rate of 2.625 points back.</p>
<p>Preferred Rewards also comes with a interest rate booster on a savings account,
from 5% on the Gold tier to a maximum of 20% at the Platinum Honors and above.
That sounds nice, except that the base Annual Percent Yield (APY) on these
savings account as of writing this, is a whopping 0.01% APY. Not 1%. 0.01%.
They're generous enough to round up, though, so Gold gets an rate of 0.02%,
Platinum Honors and higher gets an astounding 0.04%. What a steal!</p>
<p>High Yield Savings Accounts (HYSA) are FDIC-insured accounts that offer a high
interest rate for money in your account. Because they are a savings account, you
cannot ever lose numerical value in this account. They have a variable APY
though, so while it may have a high APY now, it might not in the future.</p>
<p>As of March 2023, they have an APY from anywhere of 2% to 5%.</p>
<p>If you look at just APY alone, it's clear that if you're looking to park an
emergency fund or have short-term plans in mind, it makes absolutely no sense to
keep your money at BofA. But if you attempt to consider the credit card bonus,
it might not be as clear. After all, earning an extra percent or two on your
credit card purchasing is incredibly enticing. In reality, it only makes sense
if you have an brokerage account with Merrill and you're happy with what Merrill
offers.</p>
<p>Lets consider the total value that a person gets from keeping money in a BofA
savings account and getting the credit card bonus over moving to a HYSA. We'll
say that the person wants to keep it in a savings account as they want to save
this money as an emergency fund. We'll also assume that additional points have a
100 to 1.00 USD exchange rate.</p>
<div><canvas id="myChart"></canvas></div>
<br>
<table>
<tbody><tr>
<td><label for="saved">Savings (USD)</label></td>
<td class="alignRight fill" id="savedDisplay">25%</td>
<td><input id="saved" type="range" min="0" max="120000" step="5000" value="50000"></td>
</tr>
<tr>
<td><label for="hysaPercent">HYSA APY (%)</label></td>
<td class="alignRight fill" id="hysaPercentDisplay">25%</td>
<td><input id="hysaPercent" type="range" min="0" max="9" step="0.05" value="3.75"></td>
</tr>
<tr>
<td><label for="monthlySpending">Monthly Spending (USD)</label></td>
<td class="alignRight fill" id="monthlySpendingDisplay">25%</td>
<td><input id="monthlySpending" type="range" min="0" max="10000" step="200" value="2000"></td>
</tr>
<tr>
<td><label for="avgBasePoints">Avg. points gained per USD (Without preferred rewards)</label></td>
<td class="alignRight fill" id="avgBasePointsDisplay">25%</td>
<td><input id="avgBasePoints" type="range" min="0" max="5" step="0.5" value="3"></td>
</tr>
</tbody></table>
<script src="./chart.js"></script>
<p>Playing around with the chart, you'll notice that with the bare minimum to get
the Platinum tier, generous amounts of spending on your cards, and an HYSA APY
of 0.5%, you <i>still</i> get better returns in the HYSA than keeping money in
the savings account. Even if you were to find a scenario where you are earning
more with the credit card bonus, unless you're spending that much already (and
not spending for the sake of bonus points), it makes absolutely no sense to do
so.</p>
<h2>What about Money Market Mutual Funds?</h2>
<p>Merrill also offers Money Market Mutual Funds (MMMFs) such as <a href="https://www.blackrock.com/cash/en-us/products/282697" rel="noopener noreferrer">TTTXX</a> and offer
competitive rates to a HYSA outside BofA. However, the key difference here is
that they are not FDIC-insured and run the risk of
<a href="https://www.investopedia.com/terms/b/breaking-the-buck.asp" rel="noopener noreferrer noopener noreferrer">"breaking the buck"</a> They are also much more sensitive to
fluctuations in the market. As a result, even the risk is miniscule, they are
fundamentally in a different risk class than a savings account.</p>
<p>If you think that the Preferred Rewards are valuable enough to expose your
emergency funds and/or short-term holdings to the market, then sure, it's a way
to have the best of both worlds. However, for me, I've decided that it's not,
especially with the current situation.</p>
<h2>When does it make sense?</h2>
<p>I think there are only a few real scenarios where keeping your money in a BofA
savings account is reasonable.</p>
<ul>
<li>You already have a brokerage account in Merrill that meets the minimum for a
tier.</li>
<li>You are willing to invest your savings/emergency fund into a Money Market
Mutual Fund AND think the Preferred Rewards are worthwhile.</li>
<li>You have less than 10k in savings AND HYSA APYs are below 0.7% AND you spent
over 5k per month.</li>
</ul>
<p>I put less than a couple hundreds of dollars on my BofA card, so there's almost
zero incentive for me to keep my money with BofA. In fact, with inflation as
large as it is, I'm losing an incredible amount of purchasing power by keeping
it in a BofA saving account.</p>
</body></html>

View file

@ -0,0 +1,480 @@
/*
! tailwindcss v3.2.5 | MIT License | https://tailwindcss.com
*//*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: #e5e7eb; /* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
*/
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
-o-tab-size: 4;
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
font-feature-settings: normal; /* 5 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
font-size: 1em; /* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.flex {
display: flex;
}
.table {
display: table;
}
.hidden {
display: none;
}
/*# sourceMappingURL=./styles.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sources":["/src/styles.css"],"names":[],"mappings":"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","file":"/styles.css.map","sourcesContent":["@tailwind base;\n@tailwind components;\n@tailwind utilities;"],"sourceRoot":"file:///home/edward/Documents/repos/websites/eddie.sh-deno"}

16
eddie.sh-deno/deno.json Normal file
View file

@ -0,0 +1,16 @@
{
"tasks": {
"lume": "echo \"import 'lume/cli.ts'\" | deno run --unstable -A -",
"build": "deno task lume",
"serve": "deno task lume -s"
},
"importMap": "import_map.json",
"compilerOptions": {
"lib": [
"dom",
"deno.window"
],
"jsx": "react-jsx",
"jsxImportSource": "npm:react"
}
}

2186
eddie.sh-deno/deno.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
{
"imports": {
"lume/": "https://deno.land/x/lume@v1.16.2/",
"react": "https://esm.sh/react@18.2.0/"
}
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }}</title>
<link rel="icon" href="data:,">
</head>
<body>
{{ content | safe }}
</body>
</html>

View file

@ -0,0 +1,60 @@
<head>
<title>Edward Shen</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Code+Latin&display=swap" rel="stylesheet">
<!-- <style src="./styles.css"></style> -->
<!-- <style>
html {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
}
body {
font-family: 'M PLUS Code Latin', sans-serif;
background-color: #000;
max-width: 84ch;
width: 84ch;
padding: 0 2ch;
color: #ccc;
overflow: hidden;
}
a:visited {
color: #999;
}
a {
color: #ccc;
}
ul {
list-style-type: "> ";
}
</style> -->
</head>
# Edward Shen
Rust; Rhythm games; Food; Homebrewing; Security; Scalability; Free software.
[Github]
<br />
Interesting projects:
- [OmegaUpload]
- [bunbun]
- [Shlink browser extension]
- [ayaya]
[Github]: https://github.com/edward-shen
[OmegaUpload]: https://github.com/edward-shen/omegaupload
[bunbun]: https://github.com/edward-shen/bunbun
[Shlink browser extension]: https://github.com/edward-shen/shlink
[ayaya]: https://github.com/edward-shen/ayaya

View file

@ -0,0 +1 @@
layout: layouts/oneshots.njk

View file

@ -0,0 +1,195 @@
import { Chart, registerables } from "https://esm.sh/chart.js@4";
Chart.register(...registerables);
function monthlyInterest(apy: number) {
return Math.pow(1 + apy, 1 / 12) - 1;
}
function rawGrowth(base: number, monthlyFunc: (_: number) => number) {
const data: number[] = Array(13).fill(base);
for (let i = 1; i < data.length; i++) {
data[i] = (data[i - 1] * (1 + monthlyFunc(data[i - 1])));
}
return data;
}
const PLAT_HONORS_THRESHOLD = 100_000;
const PLAT_THRESHOLD = 50_000;
const GOLD_THRESHOLD = 20_000;
function bofaInterestRateFromSavings(savings: number) {
if (savings >= PLAT_HONORS_THRESHOLD) {
return 0.0004;
} else if (savings >= PLAT_THRESHOLD) {
return 0.0003;
} else if (savings >= GOLD_THRESHOLD) {
return 0.0002;
} else {
return 0.0001;
}
}
function bofaAdditionalPoints(basePoints: number, savings: number) {
if (savings >= PLAT_HONORS_THRESHOLD) {
return basePoints * 0.75;
} else if (savings >= PLAT_THRESHOLD) {
return basePoints * 0.5;
} else if (savings >= GOLD_THRESHOLD) {
return basePoints * 0.25;
} else {
return basePoints;
}
}
function hysaGrowth(base: number, monthly: number) {
return rawGrowth(base, () => monthly).map((v) => v - base);
}
function bofaSavingsGrowth(base: number) {
return rawGrowth(base, (savings) => bofaInterestRateFromSavings(savings)).map(
(v) => v - base
);
}
function creditCardGrowth(
baseSaved: number,
basePoints: number,
monthlySpending: number,
) {
return bofaSavingsGrowth(baseSaved).map((growth: number) =>
monthlySpending * bofaAdditionalPoints(basePoints, baseSaved + growth) / 100
);
}
function bofaCombined(
baseSaved: number,
basePoints: number,
monthlySpending: number,
) {
const savings = bofaSavingsGrowth(baseSaved);
return creditCardGrowth(baseSaved, basePoints, monthlySpending).map((v, i) =>
v + savings[i]
);
}
function bofaGrossCombined(
baseSaved: number,
basePoints: number,
monthlySpending: number,
) {
const savings = bofaSavingsGrowth(baseSaved);
return creditCardGrowth(baseSaved, basePoints, monthlySpending).map((v, i) =>
v + savings[i] - monthlySpending * i
);
}
function getDatasets(
saved: number,
hysaPercent: number,
monthlySpending: number,
avgBasePoints: number,
) {
return [
{
label: "HYSA",
data: hysaGrowth(saved, monthlyInterest(hysaPercent / 100)).map(v => v.toFixed(2)),
borderWidth: 1,
},
{
label: "BofA Savings Interest",
data: bofaSavingsGrowth(saved).map(v => v.toFixed(2)),
borderWidth: 1,
},
{
label: "Credit Card Bonus",
data: creditCardGrowth(saved, avgBasePoints, monthlySpending).map(v => v.toFixed(2)),
borderWidth: 1,
},
{
label: "BofA Savings Interest + Credit Card Bonus",
data: bofaCombined(saved, avgBasePoints, monthlySpending).map(v => v.toFixed(2)),
borderWidth: 1,
},
{
label: "BofA Savings Interest + Credit Card Bonus - Amount spent",
data: bofaGrossCombined(saved, avgBasePoints, monthlySpending).map(v => v.toFixed(2)),
borderWidth: 1,
hidden: true,
},
];
}
document.addEventListener("DOMContentLoaded", () => {
function getOrInit(id: string, value: number): number {
const ele = document.getElementById(id) as HTMLInputElement;
if (ele.value) {
document.getElementById(id + "Display").textContent = ele.value;
return Number(ele.value);
} else {
document.getElementById(id + "Display").textContent = String(value);
ele.value = String(value);
return value;
}
}
let saved = getOrInit("saved", 50_000);
let hysaPercent = getOrInit("hysaPercent", 3.75);
let monthlySpending = getOrInit("monthlySpending", 2_000);
let avgBasePoints = getOrInit("avgBasePoints", 1.5);
Chart.defaults.color = "#ccc";
Chart.defaults.borderColor = "#9993";
Chart.defaults.font.family = "'M PLUS Code Latin', sans-serif";
Chart.defaults.font.size = 16;
const chart = new Chart(document.getElementById('myChart'), {
normalized: true,
type: 'line',
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"],
datasets: getDatasets(saved, hysaPercent, monthlySpending, avgBasePoints),
},
options: {
scales: {
y: {
beginAtZero: true,
}
},
animation: false,
}
});
function updateChart() {
chart.data.datasets = getDatasets(saved, hysaPercent, monthlySpending, avgBasePoints);
chart.update();
}
document.getElementById("saved")?.addEventListener("input", (ev) => {
if (ev.target.value) {
saved = ev.target.value;
document.getElementById("savedDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("monthlySpending")?.addEventListener("input", (ev) => {
if (ev.target.value) {
monthlySpending = ev.target.value;
document.getElementById("monthlySpendingDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("avgBasePoints")?.addEventListener("input", (ev) => {
if (ev.target.value) {
avgBasePoints = ev.target.value;
document.getElementById("avgBasePointsDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("hysaPercent")?.addEventListener("input", (ev: Event) => {
const value = (ev.target as HTMLInputElement).value;
if (value) {
hysaPercent = Number(value);
document.getElementById("hysaPercentDisplay").textContent = value;
updateChart();
}
});
});

View file

@ -0,0 +1,123 @@
---
title: Bank of America Preferred Rewards Probably Suck
---
# Bank of America Preferred Rewards Probably Suck
### 2023-04-03
Bank of America (BofA) has a rewards program that is tiered based on how much of
money you have with BofA or an associated Merrill account. There are five tiers
that they offer, with (among other things) a credit card percent bonus:
| Tier Name | Minimum Balance Required (USD) | Credit Card Bonus |
| --------------- | -----------------------------: | ----------------: |
| Gold | 20,000 | 25% |
| Platinum | 50,000 | 50% |
| Platinum Honors | 100,000 | 75% |
| Diamond | 1,000,000 | 75% |
| Diamond Honors | 10,000,000 | 75% |
The credit card percent bonus is an additional amount of points or cash back
based on how much you original were going to get. For example, a 75% percent
bonus on a 1.5 points per dollar purchase gives you an additional 1.125 points,
or an effective rate of 2.625 points back.
Preferred Rewards also comes with a interest rate booster on a savings account,
from 5% on the Gold tier to a maximum of 20% at the Platinum Honors and above.
That sounds nice, except that the base Annual Percent Yield (APY) on these
savings account as of writing this, is a whopping 0.01% APY. Not 1%. 0.01%.
They're generous enough to round up, though, so Gold gets an rate of 0.02%,
Platinum Honors and higher gets an astounding 0.04%. What a steal!
High Yield Savings Accounts (HYSA) are FDIC-insured accounts that offer a high
interest rate for money in your account. Because they are a savings account, you
cannot ever lose numerical value in this account. They have a variable APY
though, so while it may have a high APY now, it might not in the future.
As of March 2023, they have an APY from anywhere of 2% to 5%.
If you look at just APY alone, it's clear that if you're looking to park an
emergency fund or have short-term plans in mind, it makes absolutely no sense to
keep your money at BofA. But if you attempt to consider the credit card bonus,
it might not be as clear. After all, earning an extra percent or two on your
credit card purchasing is incredibly enticing. In reality, it only makes sense
if you have an brokerage account with Merrill and you're happy with what Merrill
offers.
Lets consider the total value that a person gets from keeping money in a BofA
savings account and getting the credit card bonus over moving to a HYSA. We'll
say that the person wants to keep it in a savings account as they want to save
this money as an emergency fund. We'll also assume that additional points have a
100 to 1.00 USD exchange rate.
<div><canvas id="myChart"></canvas></div>
<br />
<table>
<tr>
<td><label for="saved">Savings (USD)</label></td>
<td class="alignRight fill" id="savedDisplay">25%</td>
<td><input id="saved" type="range" min="0" max="120000" step="5000" value="50000"></input></td>
</tr>
<tr>
<td><label for="hysaPercent">HYSA APY (%)</label></td>
<td class="alignRight fill" id="hysaPercentDisplay">25%</td>
<td><input id="hysaPercent" type="range" min="0" max="9" step="0.05" value="3.75"></input></td>
</tr>
<tr>
<td><label for="monthlySpending">Monthly Spending (USD)</label></td>
<td class="alignRight fill" id="monthlySpendingDisplay">25%</td>
<td><input id="monthlySpending" type="range" min="0" max="10000" step="200" value="2000"></input></td>
</tr>
<tr>
<td><label for="avgBasePoints">Avg. points gained per USD (Without preferred rewards)</label></td>
<td class="alignRight fill" id="avgBasePointsDisplay">25%</td>
<td><input id="avgBasePoints" type="range" min="0" max="5" step="0.5" value="3"></input></td>
</tr>
</table>
<script src="./chart.js"></script>
Playing around with the chart, you'll notice that with the bare minimum to get
the Platinum tier, generous amounts of spending on your cards, and an HYSA APY
of 0.5%, you <i>still</i> get better returns in the HYSA than keeping money in
the savings account. Even if you were to find a scenario where you are earning
more with the credit card bonus, unless you're spending that much already (and
not spending for the sake of bonus points), it makes absolutely no sense to do
so.
## What about Money Market Mutual Funds?
Merrill also offers Money Market Mutual Funds (MMMFs) such as [TTTXX] and offer
competitive rates to a HYSA outside BofA. However, the key difference here is
that they are not FDIC-insured and run the risk of
["breaking the buck"][breaking-buck] They are also much more sensitive to
fluctuations in the market. As a result, even the risk is miniscule, they are
fundamentally in a different risk class than a savings account.
If you think that the Preferred Rewards are valuable enough to expose your
emergency funds and/or short-term holdings to the market, then sure, it's a way
to have the best of both worlds. However, for me, I've decided that it's not,
especially with the current situation.
[TTTXX]: https://www.blackrock.com/cash/en-us/products/282697
[breaking-buck]: https://www.investopedia.com/terms/b/breaking-the-buck.asp
## When does it make sense?
I think there are only a few real scenarios where keeping your money in a BofA
savings account is reasonable.
- You already have a brokerage account in Merrill that meets the minimum for a
tier.
- You are willing to invest your savings/emergency fund into a Money Market
Mutual Fund AND think the Preferred Rewards are worthwhile.
- You have less than 10k in savings AND HYSA APYs are below 0.7% AND you spent
over 5k per month.
I put less than a couple hundreds of dollars on my BofA card, so there's almost
zero incentive for me to keep my money with BofA. In fact, with inflation as
large as it is, I'm losing an incredible amount of purchasing power by keeping
it in a BofA saving account.

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

21
eddie.sh/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Edward Shen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

67
eddie.sh/index.html Normal file
View file

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edward Shen</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Code+Latin&display=swap" rel="stylesheet">
<!-- If anyone is snooping around and would like to contribute a favicon, let me know :P -->
<link rel="icon" href="data:,">
<style>
html {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
}
body {
font-family: 'M PLUS Code Latin', sans-serif;
background-color: #000;
max-width: 84ch;
width: 84ch;
padding: 0 2ch;
color: #ccc;
overflow: hidden;
}
a:visited {
color: #999;
}
a {
color: #ccc;
}
ul {
list-style-type: "> ";
}
</style>
</head>
<body>
<h1>Edward Shen</h1>
<p>Rust; Rhythm games; Food; Homebrewing; Security; Scalability; Free software.</p>
<p>
<a href="https://github.com/edward-shen">Github</a>
</p>
<p>Email local-part: hi</p>
<br>
<p>Interesting projects:</p>
<ul>
<li><a rel="noreferrer" href="https://github.com/edward-shen/omegaupload">OmegaUpload</a></li>
<li><a rel="noreferrer" href="https://github.com/edward-shen/bunbun">bunbun</a></li>
<li><a rel="noreferrer" href="https://github.com/edward-shen/shlink">Shlink browser extension</a></li>
<li><a rel="noreferrer" href="https://github.com/edward-shen/ayaya">ayaya</a></li>
</ul>
</body>
</html>

View file

@ -0,0 +1,404 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bank of America Preferred Rewards Suck</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Code+Latin&display=swap" rel="stylesheet">
<!-- If anyone is snooping around and would like to contribute a favicon, let me know :P -->
<link rel="icon" href="data:,">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<style>
html {
display: flex;
justify-content: center;
align-items: center;
}
body {
font-family: 'M PLUS Code Latin', sans-serif;
background-color: #000;
max-width: 84ch;
width: 84ch;
padding: 0 2ch;
margin-top: 10vh;
color: #ccc;
font-size: 16pt;
}
a:visited {
color: #999;
}
a {
color: #ccc;
}
ul {
list-style-type: "> ";
}
.alignRight {
text-align: right;
}
table {
width: 100%;
}
.fill {
min-width: 6ch;
}
</style>
</head>
<body>
<h1>Bank of America Preferred Rewards Probably Suck</h1>
<h3>2023-04-03</h3>
<p>
Bank of America (BofA) has a rewards program that is tiered based on how
much of money you have with BofA or an associated Merrill account. There are
five tiers that they offer, with (among other things) a credit card percent
bonus:
</p>
<table>
<tr>
<th>Tier Name</th>
<th>Minimum Balance Required (USD)</th>
<th>Credit Card Bonus</th>
</tr>
<tr>
<td>Gold</td>
<td class="alignRight">20,000</td>
<td class="alignRight">25%</td>
</tr>
<tr>
<td>Platinum</td>
<td class="alignRight">50,000</td>
<td class="alignRight">50%</td>
</tr>
<tr>
<td>Platinum Honors</td>
<td class="alignRight">100,000</td>
<td class="alignRight">75%</td>
</tr>
<tr>
<td>Diamond</td>
<td class="alignRight">1,000,000</td>
<td class="alignRight">75%</td>
</tr>
<tr>
<td>Diamond Honors</td>
<td class="alignRight">10,000,000</td>
<td class="alignRight">75%</td>
</tr>
</table>
<p>
The credit card percent bonus is an additional amount of points or cash back
based on how much you original were going to get. For example, a 75% percent
bonus on a 1.5 points per dollar purchase gives you an additional 1.125
points, or an effective rate of 2.625 points back.
</p>
<p>
Preferred Rewards also comes with a interest rate booster on a savings
account, from 5% on the Gold tier to a maximum of 20% at the Platinum Honors
and above. That sounds nice, except that the base Annual Percent Yield (APY)
on these savings account as of writing this, is a whopping 0.01% APY. Not
1%. 0.01%. They're generous enough to round up, though, so Gold gets an rate
of 0.02%, Platinum Honors and higher gets an astounding 0.04%. What a steal!
</p>
<p>
High Yield Savings Accounts (HYSA) are FDIC-insured accounts that offer a
high interest rate for money in your account. Because they are a savings
account, you cannot ever lose numerical value in this account. They have a
variable APY though, so while it may have a high APY now, it might not in
the future.
</p>
<p>As of March 2023, they have an APY from anywhere of 2% to 5%.</p>
<p>
If you look at just APY alone, it's clear that if you're looking to park an
emergency fund or have short-term plans in mind, it makes absolutely no
sense to keep your money at BofA. But if you attempt to consider the credit
card bonus, it might not be as clear. After all, earning an extra percent or
two on your credit card purchasing is incredibly enticing. In reality, it
only makes sense if you have an brokerage account with Merrill and you're
happy with what Merrill offers.
</p>
<p>
Lets consider the total value that a person gets from keeping money in a
BofA savings account and getting the credit card bonus over moving to a
HYSA. We'll say that the person wants to keep it in a savings account as
they want to save this money as an emergency fund. We'll also assume that
additional points have a 100 to 1.00 USD exchange rate.
</p>
<div>
<canvas id="myChart"></canvas>
</div>
<br />
<table>
<tr>
<td><label for="saved">Savings (USD)</label></td>
<td class="alignRight fill" id="savedDisplay">25%</td>
<td><input id="saved" type="range" min="0" max="120000" step="5000" value="50000"></input></td>
</tr>
<tr>
<td><label for="hysaPercent">HYSA APY (%)</label></td>
<td class="alignRight fill" id="hysaPercentDisplay">25%</td>
<td><input id="hysaPercent" type="range" min="0" max="9" step="0.05" value="3.75"></input></td>
</tr>
<tr>
<td><label for="monthlySpending">Monthly Spending (USD)</label></td>
<td class="alignRight fill" id="monthlySpendingDisplay">25%</td>
<td><input id="monthlySpending" type="range" min="0" max="10000" step="200" value="2000"></input></td>
</tr>
<tr>
<td><label for="avgBasePoints">Avg. points gained per USD (Without preferred rewards)</label></td>
<td class="alignRight fill" id="avgBasePointsDisplay">25%</td>
<td><input id="avgBasePoints" type="range" min="0" max="5" step="0.5" value="3"></input></td>
</tr>
</table>
<p>
Playing around with the chart, you'll notice that with the bare minimum to
get the Platinum tier, generous amounts of spending on your cards, and an
HYSA APY of 0.5%, you <i>still</i> get better returns in the HYSA than
keeping money in the savings account. Even if you were to find a scenario
where you are earning more with the credit card bonus, unless you're
spending that much already (and not spending for the sake of bonus points),
it makes absolutely no sense to do so.
</p>
<h2>What about Money Market Mutual Funds?</h2>
<p>
Merrill also offers Money Market Mutual Funds (MMMFs) such as <a
href="https://www.blackrock.com/cash/en-us/products/282697/">TTTXX</a> and
offer competitive rates to a HYSA outside BofA. However, the key difference
here is that they are not FDIC-insured and run the risk of <a
href="https://www.investopedia.com/terms/b/breaking-the-buck.asp">"breaking the
buck"</a>. They are also much more sensitive to fluctuations in the
market. As a result, even the risk is miniscule, they are fundamentally
in a different risk class than a savings account.
</p>
<p>
If you think that the Preferred Rewards are valuable enough to expose your
emergency funds and/or short-term holdings to the market, then sure, it's a
way to have the best of both worlds. However, for me, I've decided that it's
not, especially with the current situation.
</p>
<h2>When does it make sense?</h2>
<p>
I think there are only a few real scenarios where keeping your money in a
BofA savings account is reasonable.
</p>
<ol>
<li>You already have a brokerage account in Merrill that meets the minimum for a tier.</li>
<li>You are willing to invest your savings/emergency fund into a Money Market Mutual Fund AND think the Preferred
Rewards are worthwhile.</li>
<li>You have less than 10k in savings AND HYSA APYs are below 0.7% AND you spent over 5k per month.</li>
</ol>
<p>
I put less than a couple hundreds of dollars on my BofA card, so there's
almost zero incentive for me to keep my money with BofA. In fact, with
inflation as large as it is, I'm losing an incredible amount of purchasing
power by keeping it in a BofA saving account.
</p>
<script>
function monthlyInterest(apy) {
return Math.pow(1 + apy, 1 / 12) - 1;
}
function rawGrowth(base, monthlyFunc) {
let data = Array(13).fill(base);
for (let i = 1; i < data.length; i++) {
data[i] = (data[i - 1] * (1 + monthlyFunc(data[i - 1]))).toFixed(2);
}
return data;
}
const PLAT_HONORS_THRESHOLD = 100_000;
const PLAT_THRESHOLD = 50_000;
const GOLD_THRESHOLD = 20_000;
function bofaInterestRateFromSavings(savings) {
if (savings >= PLAT_HONORS_THRESHOLD) {
return 0.0004;
} else if (savings >= PLAT_THRESHOLD) {
return 0.0003;
} else if (savings >= GOLD_THRESHOLD) {
return 0.0002;
} else {
return 0.0001;
}
}
function bofaAdditionalPoints(basePoints, savings) {
if (savings >= PLAT_HONORS_THRESHOLD) {
return basePoints * 0.75;
} else if (savings >= PLAT_THRESHOLD) {
return basePoints * 0.5;
} else if (savings >= GOLD_THRESHOLD) {
return basePoints * 0.25;
} else {
return basePoints;
}
}
function hysaGrowth(base, monthly) {
return rawGrowth(base, () => monthly).map(v => v - base);
}
function bofaSavingsGrowth(base) {
return rawGrowth(base, savings => bofaInterestRateFromSavings(savings)).map(v => v - base);
}
function creditCardGrowth(baseSaved, basePoints, monthlySpending) {
return bofaSavingsGrowth(baseSaved).map(growth => monthlySpending * bofaAdditionalPoints(basePoints, baseSaved + growth) / 100);
}
function bofaCombined(baseSaved, basePoints, monthlySpending) {
let savings = bofaSavingsGrowth(baseSaved);
return creditCardGrowth(baseSaved, basePoints, monthlySpending).map((v, i) => v + savings[i]);
}
function bofaGrossCombined(baseSaved, basePoints, monthlySpending) {
let savings = bofaSavingsGrowth(baseSaved);
return creditCardGrowth(baseSaved, basePoints, monthlySpending).map((v, i) => v + savings[i] - monthlySpending * i);
}
function getDatasets(saved, hysaPercent, monthlySpending, avgBasePoints) {
return [
{
label: 'HYSA',
data: hysaGrowth(saved, monthlyInterest(hysaPercent / 100)),
borderWidth: 1
},
{
label: 'BofA Savings Interest',
data: bofaSavingsGrowth(saved),
borderWidth: 1
},
{
label: 'Credit Card Bonus',
data: creditCardGrowth(saved, avgBasePoints, monthlySpending),
borderWidth: 1
},
{
label: 'BofA Savings Interest + Credit Card Bonus',
data: bofaCombined(saved, avgBasePoints, monthlySpending),
borderWidth: 1
},
{
label: 'BofA Savings Interest + Credit Card Bonus - Amount spent',
data: bofaGrossCombined(saved, avgBasePoints, monthlySpending),
borderWidth: 1,
hidden: true,
}
];
}
document.addEventListener("DOMContentLoaded", () => {
function getOrInit(id, value) {
const ele = document.getElementById(id);
if (ele.value) {
document.getElementById(id + "Display").textContent = ele.value;
return ele.value;
} else {
document.getElementById(id + "Display").textContent = value;
ele.value = value;
return value;
}
}
let saved = getOrInit("saved", 50_000);
let hysaPercent = getOrInit("hysaPercent", 3.75);
let monthlySpending = getOrInit("monthlySpending", 2_000);
let avgBasePoints = getOrInit("avgBasePoints", 1.5);
Chart.defaults.color = "#ccc";
Chart.defaults.borderColor = "#9993";
Chart.defaults.font.family = "'M PLUS Code Latin', sans-serif";
Chart.defaults.font.size = 16;
let chart = new Chart(document.getElementById('myChart'), {
normalized: true,
type: 'line',
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"],
datasets: getDatasets(saved, hysaPercent, monthlySpending, avgBasePoints),
},
options: {
scales: {
y: {
beginAtZero: true,
}
},
animation: false,
}
});
function updateChart() {
chart.data.datasets = getDatasets(saved, hysaPercent, monthlySpending, avgBasePoints);
chart.update();
}
document.getElementById("saved").addEventListener("input", (ev) => {
if (ev.target.value) {
saved = ev.target.value;
document.getElementById("savedDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("monthlySpending").addEventListener("input", (ev) => {
if (ev.target.value) {
monthlySpending = ev.target.value;
document.getElementById("monthlySpendingDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("avgBasePoints").addEventListener("input", (ev) => {
if (ev.target.value) {
avgBasePoints = ev.target.value;
document.getElementById("avgBasePointsDisplay").textContent = ev.target.value;
updateChart();
}
});
document.getElementById("hysaPercent").addEventListener("input", (ev) => {
if (ev.target.value) {
hysaPercent = ev.target.value;
document.getElementById("hysaPercentDisplay").textContent = ev.target.value;
updateChart();
}
});
});
</script>
</body>
</html>

BIN
eddie.sh/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
eddie.sh/resume.pdf Normal file

Binary file not shown.

BIN
eddie.sh/resume_beta.pdf Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,5 @@
<svg width="566" height="566" viewBox="0 0 566 566" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:difference">
<path fill-rule="evenodd" clip-rule="evenodd" d="M283 547C428.803 547 547 428.803 547 283C547 137.197 428.803 19 283 19C137.197 19 19 137.197 19 283C19 428.803 137.197 547 283 547ZM271.125 134.844C278.965 105.726 283 74.5173 283 43C283 74.5173 287.035 105.726 294.875 134.844C302.715 163.962 314.205 190.42 328.691 212.706C343.177 234.992 360.375 252.67 379.301 264.731C398.228 276.792 418.514 283 439 283C418.514 283 398.228 289.208 379.301 301.269C360.375 313.33 343.177 331.008 328.691 353.294C314.205 375.58 302.715 402.038 294.875 431.156C287.035 460.274 283 491.483 283 523C283 491.483 278.965 460.274 271.125 431.156C263.286 402.038 251.795 375.58 237.309 353.294C222.823 331.008 205.625 313.33 186.699 301.269C167.772 289.208 147.486 283 127 283C147.486 283 167.772 276.792 186.699 264.731C205.625 252.67 222.823 234.992 237.309 212.706C251.795 190.42 263.285 163.962 271.125 134.844ZM14.9427 550.094C64.8826 600.034 225.165 520.721 372.943 372.943C520.721 225.165 600.034 64.8826 550.094 14.9427C527.264 -7.88818 481.371 -3.70484 424.154 21.6844C446.406 16.9824 463.712 19.2408 474.026 29.555C511.888 67.4168 441.197 199.494 316.133 324.558C191.07 449.621 58.9925 520.312 21.1307 482.45C13.9305 475.25 10.6561 464.643 10.9348 451.324C-4.35045 495.795 -4.14607 531.006 14.9427 550.094Z" fill="#D9D9D9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

71
starry.network/index.html Normal file
View file

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Starry Network</title>
<meta name="description" content="A starry jet in the Andromeda.">
<link rel="icon" href="favicon.svg">
<link href="stars.css" rel="stylesheet">
<style>
@font-face {
font-family: 'M PLUS Code Latin';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(MPLUSCodeLatin.subset.woff2) format('woff2');
unicode-range: U+0000-007F;
}
html {
background: linear-gradient(to bottom, #2a2981 0%, #13151d 40%, #000 100%);
height: 100%;
margin: 0;
padding: 0;
}
body {
font-family: 'M PLUS Code Latin', sans-serif;
color: #ccc;
overflow: hidden;
height: 100%;
margin: 0;
padding: 0;
}
#wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
main {
max-width: 80ch;
width: 80ch;
padding: 0 2ch;
}
</style>
</head>
<body>
<div id='stars'></div>
<div id='stars2'></div>
<div id='stars3'></div>
<div id="wrapper">
<main>
<h1>starry.network</h1>
<p>Like a dancer in the dark,</p>
<p>Illuminated only by a stellar comet,</p>
<p>A bluerose diamond dazzles,</p>
<p>Beyond the Never Ending Midnights.</p>
</main>
</div>
</body>
</html>

90
starry.network/stars.css Normal file
View file

@ -0,0 +1,90 @@
/*
Copyright (c) 2022 by sarazond (https://codepen.io/sarazond/pen/LYGbwj)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
html {
height: 100%;
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
overflow: hidden;
}
#stars {
width: 1px;
height: 1px;
background: transparent;
box-shadow: 138vw 82vh #FFF, 11vw 46vh #FFF, 84vw 100vh #FFF, 129vw 190vh #FFF, 66vw 104vh #FFF, 131vw 98vh #FFF, 45vw 123vh #FFF, 141vw 126vh #FFF, 100vw 78vh #FFF, 158vw 153vh #FFF, 130vw 98vh #FFF, 167vw 100vh #FFF, 66vw 175vh #FFF, 13vw 133vh #FFF, 194vw 63vh #FFF, 62vw 21vh #FFF, 127vw 18vh #FFF, 93vw 145vh #FFF, 167vw 145vh #FFF, 181vw 186vh #FFF, 159vw 194vh #FFF, 187vw 125vh #FFF, 48vw 46vh #FFF, 175vw 5vh #FFF, 132vw 45vh #FFF, 85vw 175vh #FFF, 138vw 89vh #FFF, 37vw 182vh #FFF, 112vw 180vh #FFF, 47vw 76vh #FFF, 20vw 19vh #FFF, 105vw 61vh #FFF, 179vw 97vh #FFF, 163vw 40vh #FFF, 99vw 17vh #FFF, 195vw 190vh #FFF, 39vw 7vh #FFF, 196vw 8vh #FFF, 112vw 132vh #FFF, 136vw 121vh #FFF, 174vw 59vh #FFF, 128vw 17vh #FFF, 133vw 9vh #FFF, 144vw 118vh #FFF, 54vw 132vh #FFF, 66vw 144vh #FFF, 152vw 121vh #FFF, 151vw 4vh #FFF, 89vw 85vh #FFF, 171vw 123vh #FFF, 171vw 133vh #FFF, 173vw 73vh #FFF, 193vw 9vh #FFF, 139vw 176vh #FFF, 68vw 140vh #FFF, 150vw 174vh #FFF, 17vw 86vh #FFF, 74vw 77vh #FFF, 146vw 192vh #FFF, 109vw 174vh #FFF, 185vw 51vh #FFF, 48vw 82vh #FFF, 176vw 159vh #FFF, 18vw 94vh #FFF, 37vw 121vh #FFF, 124vw 93vh #FFF, 134vw 12vh #FFF, 6vw 115vh #FFF, 116vw 171vh #FFF, 24vw 111vh #FFF, 95vw 99vh #FFF, 66vw 177vh #FFF, 5vw 104vh #FFF, 134vw 10vh #FFF, 152vw 96vh #FFF, 37vw 91vh #FFF, 52vw 69vh #FFF, 70vw 2vh #FFF, 55vw 5vh #FFF, 74vw 184vh #FFF, 66vw 38vh #FFF, 175vw 31vh #FFF, 53vw 47vh #FFF, 28vw 12vh #FFF, 67vw 120vh #FFF, 120vw 146vh #FFF, 144vw 125vh #FFF, 7vw 79vh #FFF, 77vw 112vh #FFF, 142vw 129vh #FFF, 175vw 42vh #FFF, 153vw 3vh #FFF, 39vw 122vh #FFF, 28vw 47vh #FFF, 134vw 47vh #FFF, 164vw 119vh #FFF, 42vw 38vh #FFF, 19vw 120vh #FFF, 143vw 154vh #FFF, 37vw 127vh #FFF;
animation: animStar 50s linear infinite;
}
#stars:after {
content: " ";
position: absolute;
top: 2000px;
width: 1px;
height: 1px;
background: transparent;
box-shadow: 138vw 82vh #FFF, 11vw 46vh #FFF, 84vw 100vh #FFF, 129vw 190vh #FFF, 66vw 104vh #FFF, 131vw 98vh #FFF, 45vw 123vh #FFF, 141vw 126vh #FFF, 100vw 78vh #FFF, 158vw 153vh #FFF, 130vw 98vh #FFF, 167vw 100vh #FFF, 66vw 175vh #FFF, 13vw 133vh #FFF, 194vw 63vh #FFF, 62vw 21vh #FFF, 127vw 18vh #FFF, 93vw 145vh #FFF, 167vw 145vh #FFF, 181vw 186vh #FFF, 159vw 194vh #FFF, 187vw 125vh #FFF, 48vw 46vh #FFF, 175vw 5vh #FFF, 132vw 45vh #FFF, 85vw 175vh #FFF, 138vw 89vh #FFF, 37vw 182vh #FFF, 112vw 180vh #FFF, 47vw 76vh #FFF, 20vw 19vh #FFF, 105vw 61vh #FFF, 179vw 97vh #FFF, 163vw 40vh #FFF, 99vw 17vh #FFF, 195vw 190vh #FFF, 39vw 7vh #FFF, 196vw 8vh #FFF, 112vw 132vh #FFF, 136vw 121vh #FFF, 174vw 59vh #FFF, 128vw 17vh #FFF, 133vw 9vh #FFF, 144vw 118vh #FFF, 54vw 132vh #FFF, 66vw 144vh #FFF, 152vw 121vh #FFF, 151vw 4vh #FFF, 89vw 85vh #FFF, 171vw 123vh #FFF, 171vw 133vh #FFF, 173vw 73vh #FFF, 193vw 9vh #FFF, 139vw 176vh #FFF, 68vw 140vh #FFF, 150vw 174vh #FFF, 17vw 86vh #FFF, 74vw 77vh #FFF, 146vw 192vh #FFF, 109vw 174vh #FFF, 185vw 51vh #FFF, 48vw 82vh #FFF, 176vw 159vh #FFF, 18vw 94vh #FFF, 37vw 121vh #FFF, 124vw 93vh #FFF, 134vw 12vh #FFF, 6vw 115vh #FFF, 116vw 171vh #FFF, 24vw 111vh #FFF, 95vw 99vh #FFF, 66vw 177vh #FFF, 5vw 104vh #FFF, 134vw 10vh #FFF, 152vw 96vh #FFF, 37vw 91vh #FFF, 52vw 69vh #FFF, 70vw 2vh #FFF, 55vw 5vh #FFF, 74vw 184vh #FFF, 66vw 38vh #FFF, 175vw 31vh #FFF, 53vw 47vh #FFF, 28vw 12vh #FFF, 67vw 120vh #FFF, 120vw 146vh #FFF, 144vw 125vh #FFF, 7vw 79vh #FFF, 77vw 112vh #FFF, 142vw 129vh #FFF, 175vw 42vh #FFF, 153vw 3vh #FFF, 39vw 122vh #FFF, 28vw 47vh #FFF, 134vw 47vh #FFF, 164vw 119vh #FFF, 42vw 38vh #FFF, 19vw 120vh #FFF, 143vw 154vh #FFF, 37vw 127vh #FFF;
}
#stars2 {
width: 2px;
height: 2px;
background: transparent;
box-shadow: 15vw 70vh #FFF, 128vw 67vh #FFF, 181vw 150vh #FFF, 42vw 58vh #FFF, 160vw 128vh #FFF, 148vw 51vh #FFF, 79vw 186vh #FFF, 47vw 37vh #FFF, 200vw 25vh #FFF, 128vw 6vh #FFF, 149vw 15vh #FFF, 55vw 127vh #FFF, 139vw 99vh #FFF, 86vw 197vh #FFF, 131vw 189vh #FFF, 131vw 20vh #FFF, 83vw 196vh #FFF, 35vw 60vh #FFF, 57vw 152vh #FFF, 124vw 6vh #FFF, 9vw 39vh #FFF, 132vw 109vh #FFF, 124vw 123vh #FFF, 176vw 96vh #FFF, 27vw 147vh #FFF, 57vw 106vh #FFF, 59vw 53vh #FFF, 177vw 65vh #FFF, 126vw 168vh #FFF, 174vw 152vh #FFF, 102vw 45vh #FFF, 29vw 188vh #FFF, 125vw 42vh #FFF, 184vw 154vh #FFF, 199vw 75vh #FFF, 165vw 108vh #FFF, 29vw 10vh #FFF, 190vw 185vh #FFF, 20vw 73vh #FFF, 179vw 117vh #FFF, 44vw 95vh #FFF, 26vw 191vh #FFF, 170vw 32vh #FFF, 34vw 120vh #FFF, 126vw 187vh #FFF, 49vw 121vh #FFF, 156vw 122vh #FFF, 80vw 188vh #FFF, 107vw 75vh #FFF, 60vw 195vh #FFF;
animation: animStar 100s linear infinite;
}
#stars2:after {
content: " ";
position: absolute;
top: 2000px;
width: 2px;
height: 2px;
background: transparent;
box-shadow: 15vw 70vh #FFF, 128vw 67vh #FFF, 181vw 150vh #FFF, 42vw 58vh #FFF, 160vw 128vh #FFF, 148vw 51vh #FFF, 79vw 186vh #FFF, 47vw 37vh #FFF, 200vw 25vh #FFF, 128vw 6vh #FFF, 149vw 15vh #FFF, 55vw 127vh #FFF, 139vw 99vh #FFF, 86vw 197vh #FFF, 131vw 189vh #FFF, 131vw 20vh #FFF, 83vw 196vh #FFF, 35vw 60vh #FFF, 57vw 152vh #FFF, 124vw 6vh #FFF, 9vw 39vh #FFF, 132vw 109vh #FFF, 124vw 123vh #FFF, 176vw 96vh #FFF, 27vw 147vh #FFF, 57vw 106vh #FFF, 59vw 53vh #FFF, 177vw 65vh #FFF, 126vw 168vh #FFF, 174vw 152vh #FFF, 102vw 45vh #FFF, 29vw 188vh #FFF, 125vw 42vh #FFF, 184vw 154vh #FFF, 199vw 75vh #FFF, 165vw 108vh #FFF, 29vw 10vh #FFF, 190vw 185vh #FFF, 20vw 73vh #FFF, 179vw 117vh #FFF, 44vw 95vh #FFF, 26vw 191vh #FFF, 170vw 32vh #FFF, 34vw 120vh #FFF, 126vw 187vh #FFF, 49vw 121vh #FFF, 156vw 122vh #FFF, 80vw 188vh #FFF, 107vw 75vh #FFF, 60vw 195vh #FFF;
}
#stars3 {
width: 3px;
height: 3px;
background: transparent;
box-shadow: 144vw 104vh #FFF, 12vw 50vh #FFF, 28vw 153vh #FFF, 175vw 134vh #FFF, 82vw 112vh #FFF, 177vw 200vh #FFF, 45vw 154vh #FFF, 5vw 39vh #FFF, 82vw 59vh #FFF, 153vw 100vh #FFF;
animation: animStar 150s linear infinite;
}
#stars3:after {
content: " ";
position: absolute;
top: 2000px;
width: 3px;
height: 3px;
background: transparent;
box-shadow: 144vw 104vh #FFF, 12vw 50vh #FFF, 28vw 153vh #FFF, 175vw 134vh #FFF, 82vw 112vh #FFF, 177vw 200vh #FFF, 45vw 154vh #FFF, 5vw 39vh #FFF, 82vw 59vh #FFF, 153vw 100vh #FFF;
}
@keyframes animStar {
from {
transform: translateY(0px);
}
to {
transform: translateY(-2000px);
}
}