Compare commits

7 Commits

Author SHA1 Message Date
758c0cde84 Update resume
Some checks failed
Deploy Hugo site to Pages / build (push) Failing after 9s
Deploy Hugo site to Pages / deploy (push) Has been skipped
2026-04-17 12:45:50 -04:00
87159e568a Add spaces-as-models 2026-04-17 12:30:42 -04:00
643068c4ef Add catching up
Some checks failed
Deploy Hugo site to Pages / build (push) Failing after 44s
Deploy Hugo site to Pages / deploy (push) Has been skipped
2026-01-31 12:58:28 -05:00
db3fa322b1 Update resume 2026-01-16 23:31:36 -05:00
b7d64dab09 Add github link 2026-01-16 23:27:50 -05:00
Tyler Perkins
0d0a878fa7 Merge pull request #2 from Clortox/simple-overhaul
Claude cleaning
2026-01-16 23:18:01 -05:00
7a28b1dc71 Claude cleaning 2026-01-16 23:17:05 -05:00
34 changed files with 647 additions and 509 deletions

View File

@@ -5,3 +5,52 @@ This repo contains the [Hugo](https://gohugo.io) source for my personal website,
Find the active source of this repository on my [Gitea](https://git.clortox.com/Infrastructure/tylerperkins.xyz), where I make issues to track additions, and am more active to pull requests.
Find the mirror of this repository on [Github](https://github.com/Clortox/tylerperkins.xyz).
## Writing Posts
### Sidenotes / Margin Notes
This blog supports Tufte-style sidenotes that appear in the right margin on desktop and at the bottom of posts on mobile. To add a sidenote:
```markdown
This is some text{{< sidenote >}}This is a sidenote that appears in the margin{{< /sidenote >}} with more text following.
```
**Features:**
- On desktop (≥1200px): Notes appear as cards in the right margin, aligned with where they're referenced
- On mobile (<1200px): Notes appear in a "Notes" section at the end of the post
- Automatic numbering: `[0]`, `[1]`, `[2]`, etc.
- Bidirectional links: Click the number in text to jump to the note, click the number in the note to jump back
- Supports markdown and math: `$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$`
### Summary Breaks
Control what appears on the homepage and list pages using the `<!--more-->` separator:
```markdown
---
title: "My Post Title"
date: 2026-01-16
---
This is the introduction paragraph that will appear on list pages as the summary.
<!--more-->
Everything after this line only appears on the full post page.
```
Without `<!--more-->`, Hugo will automatically create a summary from the first ~70 words.
### Clickable Headings
All headings automatically get anchor links. Hover over any heading to see a `#` symbol appear. Click it to get a direct link to that section.
### Markdown Source Downloads
Every post automatically generates a downloadable markdown source file. At the bottom of each post page, you'll find a "Download Markdown" link that provides the original markdown source, including all shortcodes and formatting.
**Technical Details:**
- Markdown files are generated as `index.md` alongside `index.html` in each post directory
- The raw content includes front matter, shortcodes, and all original formatting
- This is configured via Hugo's custom output formats in `config.yaml`

View File

@@ -1,21 +1,19 @@
baseURL: 'http://tylerperkins.xyx'
baseURL: 'http://tylerperkins.xyz'
languageCode: 'en-us'
LanguageCode: 'en-us'
title: 'Tylers Website'
theme: "clortox"
markup:
goldmark:
extensions:
passthrough:
delimiters:
block:
- - \[
- \]
- - $$
- $$
inline:
- - \(
- \)
enable: true
params:
math: true
title: "Tyler Perkins"
outputs:
page:
- HTML
- markdown
outputFormats:
markdown:
mediaType: "text/markdown"
isPlainText: true
isHTML: false
permalinkable: false
name: "markdown"
path: ""
baseName: "index"
rel: "alternate"

View File

@@ -3,7 +3,10 @@ title: "Home"
date: 2023-06-27
---
Software engineer, aspiring polymath. I like to do a little bit of everything.
# Hi, I'm Tyler
Interested in proving correctness, embedded systems, system design, genetic engineering, microscopy,
mechanical engineering, pure math (mostly algebra, category theory, number theory), operating systems design,
security, and more.
software engineer, aspiring postmath
Open to research work and projects ([email me](mailto:hello@clortox.com))

View File

@@ -0,0 +1,27 @@
---
title: "Catching Up"
date: 2026-01-30T13:30:00-04:00
draft: false
---
Hi. Its been quite a while since I wrote here.
I started writing here as an exercise in the Feynman technique. For example, the vector clock post was made when I started my masters; my first class was distributed systems.
Last year I set a goal for myself to post on here once a week, and you can see for yourself how well that went.
At that point in my life, especially 2024, I had this attitude of needing to optimize. Writing was something that I wanted to do to get more out of myself.
Between a sense of burnout
{{< sidenote >}}Does anyone else experience the feeling of being overwhelmed at all they do, yet at the same time feel as though they have the potential to do so much more?{{< /sidenote >}},
and personal events, I have relaxed from this attitude.
I'm now more of a 'humanist'
{{< sidenote >}}I'm sure there is a real word for what I'm describing that I don't know{{< /sidenote >}}
now than I was before. I place more emphasis on the journey, and the emotional feedback I feel in the moment to things I do.
That's not to say I want to give in to the basic desires in the moment, as then I would never get anything done other than sitting playing games, watching youtube, and working on silly puzzles.
Rather I'm more aware of when I feel exhaustion, and listen to it rather than chastise myself, believing if I was a little stronger, I would be able to push through.
Part of me still longs for that feeling, as it was during that time that I learned the most at my job. I feel that has slowed down. I'm still struggling to come to terms with that,
and am still looking for ways to get more out of myself while preserving a 'humanist' mindset.
All of this to say, I'm attempting to write here more. But at my own pace. I hope to write to you soon, hopefully something more technical.

View File

@@ -0,0 +1,126 @@
---
title: "Spaces as Models, and the shittiest JSON validation algorithm"
date: 2026-04-17T12:00:00-04:00
draft: false
---
Today I would like to talk about a concept I have come to rely on quite a lot,
especially over the past two or three years.
Spaces, or state spaces, are a conceptual tool for understanding the world.
Boiled down, they are the practice of imagining all configurations/solutions
of a problem as points in a space,
and variables to your problem as dimensions in that space.
We from here can assign structures that are useful, such as a topology
{{<sidenote>}}A topology is a structure over a set where we decide what items in that set are in the same "neighborhood". This lets us get concepts about continuity without needing specific distances. You can roughly think about it as defining things that are "close". {{</sidenote>}},
algebra
{{<sidenote>}}A set where we define one or more binary operators over the elements of the set, that follow some set of rules, such as groups, rings, etc{{</sidenote>}},
or morphisms between spaces
{{<sidenote>}}A mapping between two spaces, usually one that preserves some useful property. It can be thought of as a formal way of defining "analogies" between problem spaces.{{</sidenote>}}.
From this we have some way to think about all "solutions" or states that our
problem can be in.
## Example
One of the most famous examples of space modeling is the Mandelbrot set.
The function here we are modeling is
$$
f_c(z) = z^2 + c
$$
where c is a complex number. The graph below is the complex plane, or the entire domain of c.
z is the value being iterated, starting at zero.
The coloring here denotes if the series below "blows up", or
approaches infinity.
$$
|f_c(0)|, |f_c(f_c(0))|, ...
$$
![Mandelbrot set](./mandelbrot.jpg)
Mandelbrot set{{<sidenote>}}By Created by Wolfgang Beyer with the program Ultra Fractal 3. - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=321973{{</sidenote>}}
Here we are modeling the "blow up" behaviour in a space, namely the space of all complex numbers.
Even without the visualization, you might have instinctively reached for a similar kind of spatial thinking.
This is such a natural way of modeling these types of problems because it's so useful.
A large amount of high-school algebra is teaching students how to think about problems in this way.
## Less obvious example
Take a JSON parser. You may write its declaration as something like,
```python
def parseJson(input: str) -> dict:
# implementation
```
This works, but you have to have cases to deal with inputs that are strings, but are not valid JSON. For example, the input "qwertyadslgkjafgjlkj" or any other random string
will not parse, and therefore you will have to throw an exception. How can we avoid this, or at least model this?
We can think of all possible finite strings
{{<sidenote>}}This structure is called a [Free Monoid](https://en.wikipedia.org/wiki/Free_monoid){{</sidenote>}}.
as nodes in a graph, and the connections between the points in our graph
being single edits. So the node representing the string "Hello" would have edges connecting to "Hello!", "Hell", and "hello".
{{<sidenote>}}This is not all nodes "Hello" would connect to, just a few examples to illustrate the point{{</sidenote>}}
This hop makes the "distance" one
{{<sidenote>}}This distance is called [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) between two strings{{</sidenote>}}
Knowing this graph represents all possible values `input` can take on above, we know there is some sub-graph
that contains all valid JSON strings (because JSON strings are strings, and therefore must be in the set of all strings).
There is a structure, or test, to determine if a string is part of this sub-graph, namely if it follows the grammar defined in
[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt). From this we could define a new type, `JSONString`, which is a subset of all strings,
that follows this structure.
Why is `JSONString` useful? Didn't we just make a wrapper around string? Yes, but now `parseJson`, whose job
is to parse JSON, does not need to worry about parsing something that is not JSON, namely any old string not in
the space of all JSON strings. Now that responsibility is on `JSONString`, which is the boundary of our system.
This lets us throw errors fast, before we waste time getting to the step of trying to parse `input`.
### On the structure of the graph of all JSON Strings
On implementing `JSONString`, how can we make use of the structure prescribed before? If the sub-graph of
all JSON strings is connected (we can walk from any JSON string to any other JSON string,
by adding, subtracting, or modifying a single character at a time),
then our implementation might be able to start at some known JSON object, like `{}`, and walk to the provided `JSONString`.
If we can walk to it on this graph, then we just proved it's JSON. Failing to find a path however is not proof that the provided
string isn't JSON, just that we couldn't find a path in our search bounds (time, steps, etc).
Of course, this is astronomically impractical. It's far cheaper to just validate the grammar of the string.
Regardless, I think it's a neat property to know about, even if it does not have an immediate application to this problem.
Is the graph of all JSON strings connected? I have a hunch, however I leave exploring that to the reader.
### On morphisms
Notice that in modeling `parseJson` we have defined a morphism between the space of all JSON strings
and a space of python values, such as lists, dicts, strings, numbers, and bools. We can think of `parseJson` as taking the space of JSON strings and mapping
it to a space of python values
{{<sidenote>}}Not the space of all python values, just those that can be represented by JSON.{{</sidenote>}}.
In some problems, this way of thinking can really make more obvious how a method (that often models the morphism)
should be implemented. For example, seeing that a morphism is a projection, collapsing some higher-dimension domain into a lower-dimensional range,
or an embedding, raising some lower-dimensional data into a higher dimension with more structure/detail, the implementation will often naturally follow.
## Theory on why
I have a personal theory on why we are so spatially inclined.
{{<sidenote>}}Based on nothing but my gut, however I'm sure there is research I'm not aware of that may back this.{{</sidenote>}}
We struggle to memorize a list of over ten numbers,
but can remember how to drive to tens to hundreds of places without issue.
This is such a useful property there are techniques dedicated to exploiting this property of our cognition
{{<sidenote>}}See [memory of loci](https://en.wikipedia.org/wiki/Method_of_loci), or generally the concept of "memory castles"{{</sidenote>}}.
Spatial modeling seems to be such an innate feature we are built for, which likely explains why this technique
feels so natural after some practice.
## Conclusion
The two examples I've provided above of viewing problems as spaces are just the beginning.
We've demonstrated this idea on both the continuous and discrete problems. This line of thinking
applies to more than just programmers; anything can be modeled as spaces, from the space
of all possible negotiations in a deal, to the internal state of some classes
of machine learning models (latent spaces), to the space of all routes you can take to drive home.
When in doubt on a problem, try thinking spatially; you may be surprised at what insights you can glean,
even if the best idea it gives you is the shittiest JSON validator ever written.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,32 +0,0 @@
{
"Contact": [
{
"name": "Email",
"url": "mailto:hello@clortox.com"
},
{
"name": "LinkedIn",
"url": "https://www.linkedin.com/in/tyler-perkins-xyz/"
},
{
"name": "Github",
"url": "https://github.com/Clortox"
}
],
"Services": [
{
"name": "Git",
"url": "https://git.clortox.com/"
},
{
"name": "Files",
"url": "https://files.clortox.com/"
},
{
"name": "Youtube Frontend",
"url": "https://watch.clortox.com/"
}
]
}

View File

@@ -1,27 +0,0 @@
{
"LeftHandSide": [
{
"name": "Home",
"url": "/"
},
{
"name": "Resume",
"url": "/resume.pdf"
},
{
"name": "Posts",
"url": "/posts/"
}
],
"RightHandSide": [
{
"name": "PGP Key",
"url": "/me.gpg"
},
{
"name": "User Portal",
"url": "https://auth.clortox.com/"
}
]
}

View File

@@ -1,65 +0,0 @@
{
"About": {
"Content": [
{
"value": "Software developer with a passion for learning everything."
},
{
"value": "Employed as a full stack developer at Etactics Inc, with a focus on back end development and system architecture."
},
{
"value": "Aspiring polymath; Jack of all trades, master of few."
},
{
"value": "Special interest in software design and architecture, pure math, and all forms of engineering."
},
{
"value": "Currently persuing a Masters in Computer Science at Georgia Tech."
}
],
"Interests": [
{
"value": "Software Architecture"
},
{
"value": "Embedded Systems"
},
{
"value": "Computer Engineering"
},
{
"value": "Mechanical Engineering"
},
{
"value": "Control Systems"
},
{
"value": "Fabrication (Welding, Woodworking, 3D Printing, Laser Cutting)"
},
{
"value": "Cars and Small Engines"
},
{
"value": "Ham Radio (CQCQCQ KE8TIZ QRK?)"
},
{
"value": "Chemistry"
},
{
"value": "Mathematics"
},
{
"value": "Firearms"
},
{
"value": "Botany"
},
{
"value": "Homesteading"
},
{
"value": "Philosophy"
}
]
}
}

View File

@@ -1,16 +0,0 @@
{{ define "main" }}
<div class="centered top-fade" style="height:100vh">
<div class="intro-content">
<h1 data-value="command not found" data-show="error-message" class="typewriter"></h1>
<div id="error-message">
<p data-value="Page not found, sorry about that"></p>
</div>
<div class="link-block">
<a href="javascript:history.back()">&lt;--&nbsp;Go back</a>
<p>&nbsp;|&nbsp;</p>
<a href="{{ .Site.BaseURL }}">Home&nbsp;--&gt;</a>
</div>
</div>
</div>
{{ end }}

View File

@@ -1,16 +0,0 @@
{{ define "main" }}
<div class="centered top-fade" style="height:100vh">
<div class="intro-content">
<h1 data-value="kernel panic!" data-show="error-message" class="typewriter"></h1>
<div id="error-message">
<p data-value="Error on our side, my bad"></p>
</div>
<div class="link-block">
<a href="javascript:history.back()">&lt;--&nbsp;Go back</a>
<p>&nbsp;|&nbsp;</p>
<a href="{{ .Site.BaseURL }}">Home&nbsp;--&gt;</a>
</div>
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,5 @@
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
<a href="#{{ .Anchor | safeURL }}" class="heading-anchor">
<span class="anchor-icon">#</span>{{ .Text | safeHTML }}
</a>
</h{{ .Level }}>

View File

@@ -1 +0,0 @@
<center><img src="{{ .Destination }}" title="{{ with .Title }}{{ . }} {{ else }}{{ .Text }}{{ end }}" alt="{{ .Text }}" /></center>

View File

@@ -1 +0,0 @@
<a href="{{ .Destination }}" title="{{ .Title }}" data-value="{{ .Text | safeHTML }}" class="scramble">{{ .Text | safeHTML }}</a>

View File

@@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ if not .IsHome }}{{ .Title }} - {{ end }}{{ .Site.Title }}</title>
<link rel="alternate" type="application/rss+xml" title="{{ .Site.Title }}" href="/index.xml">
<style>
:root {
--bg-color: #fff;
--text-color: #333;
--link-color: #0066cc;
--border-color: #ccc;
--secondary-color: #666;
--code-bg: #f4f4f4;
--code-border: #ddd;
}
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--link-color: #6ba3ff;
--border-color: #444;
--secondary-color: #999;
--code-bg: #2d2d2d;
--code-border: #444;
--shadow: 0 1px 3px rgba(0,0,0,0.4);
}
:root {
--shadow: 0 1px 3px rgba(0,0,0,0.1);
}
html {
scroll-behavior: smooth;
}
body {
font-family: Georgia, serif;
line-height: 1.6;
max-width: 650px;
margin: 40px auto;
padding: 0 20px;
color: var(--text-color);
background: var(--bg-color);
transition: background 0.3s, color 0.3s;
}
a {
color: var(--link-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1, h2, h3 {
line-height: 1.2;
}
header {
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
margin-bottom: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 {
margin: 0;
font-size: 24px;
}
.header-right {
display: flex;
gap: 15px;
align-items: center;
}
nav a {
font-size: 14px;
}
.theme-toggle {
background: none;
border: 1px solid var(--border-color);
color: var(--text-color);
cursor: pointer;
padding: 5px 10px;
border-radius: 3px;
font-size: 14px;
}
.theme-toggle:hover {
background: var(--code-bg);
}
footer {
margin-top: 50px;
padding-top: 10px;
border-top: 1px solid var(--border-color);
font-size: 14px;
color: var(--secondary-color);
}
.date {
color: var(--secondary-color);
font-size: 14px;
}
img {
max-width: 100%;
height: auto;
display: block;
}
pre {
background: var(--code-bg);
border: 1px solid var(--code-border);
padding: 10px;
overflow-x: auto;
}
code {
background: var(--code-bg);
padding: 2px 5px;
}
pre code {
background: none;
padding: 0;
}
/* Heading anchors */
.heading-anchor {
color: var(--text-color);
text-decoration: none;
}
.heading-anchor:hover {
text-decoration: none;
}
.anchor-icon {
opacity: 0;
margin-right: 5px;
color: var(--link-color);
transition: opacity 0.2s;
}
.heading-anchor:hover .anchor-icon {
opacity: 1;
}
/* Sidenotes/Margin notes */
/* Hide sidenotes by default (except on single post pages) */
.sidenote-container {
display: none;
}
.post-content .sidenote-container {
display: inline;
}
.sidenote-ref {
color: var(--link-color);
text-decoration: none;
font-size: 0.85em;
vertical-align: super;
line-height: 0;
}
/* Mobile: sidenotes at the end */
@media (max-width: 1199px) {
.post-content .sidenote-container {
display: inline;
}
.post-content .sidenote {
display: none;
}
#footnotes-section {
margin-top: 20px;
}
#footnotes-section .sidenote {
display: block;
position: static;
margin-bottom: 15px;
font-size: 0.85em;
line-height: 1.4;
padding: 10px;
background: var(--code-bg);
border-left: 2px solid var(--link-color);
border-radius: 3px;
box-shadow: var(--shadow);
}
}
/* Desktop: sidenotes in right margin */
@media (min-width: 1200px) {
/* Only expand body width when sidenotes actually exist */
body:has(.sidenote) {
max-width: 1000px;
overflow-x: hidden;
}
.post-content {
position: relative;
}
.post-content .content-wrapper {
max-width: 650px;
position: relative;
}
.post-content .sidenote-container {
position: static;
}
.post-content .sidenote {
position: absolute;
left: 690px;
width: 250px;
font-size: 0.85em;
line-height: 1.4;
padding: 10px;
margin-top: 0;
background: var(--code-bg);
border-left: 2px solid var(--link-color);
border-radius: 3px;
box-shadow: var(--shadow);
}
#footnotes-section {
display: none;
}
#sidenotes-column {
display: none;
}
}
</style>
<script>
// Theme toggle - runs immediately to prevent flash
(function() {
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
processEscapes: true
}
};
</script>
</head>
<body>
<header>
<h1><a href="/">{{ .Site.Title }}</a></h1>
<div class="header-right">
<nav>
<a href="/resume.pdf">Resume</a>
</nav>
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
<span class="theme-icon"></span>
</button>
</div>
</header>
<main>
{{ block "main" . }}{{ end }}
</main>
<footer>
<p>&copy; {{ now.Year }} {{ .Site.Title }} | <a href="/index.xml">RSS</a> | <a href="/me.gpg">PGP Key</a> | <a href="/resume.pdf">Resume</a> | <a href="https://github.com/Clortox">Github</a> </p>
</footer>
<script>
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon();
}
function updateThemeIcon() {
const theme = document.documentElement.getAttribute('data-theme');
const icon = document.querySelector('.theme-icon');
icon.textContent = theme === 'dark' ? '☀️' : '🌙';
}
updateThemeIcon();
</script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ range .Pages }}
<article style="margin-bottom: 25px;">
<h3><a href="{{ .Permalink }}">{{ .Title }}</a></h3>
<p class="date">{{ .Date.Format "January 2, 2006" }}</p>
</article>
{{ end }}
<p><a href="/">&larr; Back to home</a></p>
{{ end }}

View File

@@ -13,13 +13,11 @@
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Tyler's Posts</title>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Silly little posts I make for no one in particular</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
<language>{{.}}</language>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" -}}
@@ -30,9 +28,8 @@
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ .Content | html -}}</description>
<guid>{{ .Permalink }}</guid>
<description>{{ .Summary | html }}</description>
</item>
{{ end }}
</channel>

View File

@@ -0,0 +1,98 @@
{{ define "main" }}
<article class="post-content">
<div class="content-wrapper">
<h1>{{ .Title }}</h1>
<p class="date">{{ .Date.Format "January 2, 2006" }}</p>
{{ .Content }}
<div id="footnotes-section"></div>
</div>
</article>
<p>
<a href="/">&larr; Back to home</a>
{{ with .OutputFormats.Get "markdown" }}
| <a href="{{ .RelPermalink }}" download>Download Markdown</a>
{{ end }}
</p>
<script>
// On mobile, collect sidenotes and display at the end
let sidenotesOrganized = false;
function positionSidenotes() {
if (window.innerWidth < 1200) return;
const contentWrapper = document.querySelector('.content-wrapper');
if (!contentWrapper) return;
const sidenotes = Array.from(document.querySelectorAll('.sidenote'));
const containers = Array.from(document.querySelectorAll('.sidenote-container'));
// Position each sidenote relative to its reference point
containers.forEach((container, index) => {
const sidenote = container.querySelector('.sidenote');
if (!sidenote) return;
const containerRect = container.getBoundingClientRect();
const wrapperRect = contentWrapper.getBoundingClientRect();
// Calculate position relative to content-wrapper
const topPosition = containerRect.top - wrapperRect.top;
sidenote.style.top = topPosition + 'px';
});
// Now check for overlaps and adjust
for (let i = 0; i < sidenotes.length - 1; i++) {
const current = sidenotes[i];
const next = sidenotes[i + 1];
const currentRect = current.getBoundingClientRect();
const nextRect = next.getBoundingClientRect();
// If next sidenote overlaps with current, push it down
if (nextRect.top < currentRect.bottom + 10) {
const currentTop = parseFloat(current.style.top) || 0;
const currentHeight = currentRect.height;
next.style.top = (currentTop + currentHeight + 10) + 'px';
}
}
}
function organizeSidenotes() {
const sidenotes = document.querySelectorAll('.sidenote');
const footnotesSection = document.getElementById('footnotes-section');
if (!footnotesSection) return;
if (window.innerWidth < 1200) {
// Mobile: show at bottom
if (!sidenotesOrganized) {
footnotesSection.innerHTML = '<hr style="margin-top: 40px; border: none; border-top: 1px solid var(--border-color);"><h3>Notes</h3>';
sidenotes.forEach((sidenote) => {
const clone = sidenote.cloneNode(true);
clone.style.display = 'block';
clone.style.position = 'static';
clone.style.width = 'auto';
clone.style.marginBottom = '15px';
footnotesSection.appendChild(clone);
});
sidenotesOrganized = true;
}
} else {
// Desktop: clear footnotes section and position sidenotes
if (sidenotesOrganized) {
footnotesSection.innerHTML = '';
sidenotesOrganized = false;
}
setTimeout(positionSidenotes, 100);
}
}
organizeSidenotes();
window.addEventListener('resize', organizeSidenotes);
window.addEventListener('load', positionSidenotes);
</script>
{{ end }}

View File

@@ -0,0 +1,7 @@
---
title: "{{ .Title }}"
date: {{ .Date.Format "2006-01-02" }}
{{ if .Draft }}draft: true{{ end }}
---
{{ .RawContent }}

View File

@@ -1,32 +1,17 @@
{{ define "main" }}
<div class="centered top-fade">
<div class="intro-content">
<h1 data-value="Hi, I'm Tyler" data-show="intro-tagline" class="typewriter"></h1>
<div id="intro-tagline">
<p data-value="Software engineer, aspiring polymath"></p>
</div>
</div>
</div>
<div class="homepage-card">
<div>
<h4 data-show="about-description" data-value="uname -a" class="typewriter card-title">~ $&nbsp;</h1>
<br/>
<small>About me</small>
</div>
<div class="about-grid">
<div id="about-description" class="about-description">
{{ range .Site.Data.index.About.Content }}
<p data-value="{{ .value }}"></p>
{{ .Content }}
<h2>Recent Posts</h2>
{{ range first 10 (where .Site.RegularPages "Type" "posts") }}
<article style="margin-bottom: 30px;">
<h3><a href="{{ .Permalink }}">{{ .Title }}</a></h3>
<p class="date">{{ .Date.Format "January 2, 2006" }}</p>
{{ if .Summary }}
<p>{{ .Summary }}</p>
{{ end }}
</div>
<div class="mini-terminal">
<p data-show="about-interests" data-value="cat interests.txt | less" class="typewriter">~ $&nbsp;</p>
<div id="about-interests" class="terminal-contents">
{{ range .Site.Data.index.About.Interests }}
<p class="terminal-text" data-value="{{ .value }}"></p>
{{ end }}
</div>
</div>
</div>
</div>
</article>
{{ end }}
<p><a href="/posts/">View all posts &rarr;</a></p>
{{ end }}

View File

@@ -1,21 +0,0 @@
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
<script>
MathJax = {
tex: {
displayMath: [['\\[', '\\]'], ['$$', '$$']], // block
inlineMath: [['\\(', '\\)']] // inline
},
macros: {
vect: ["\\boldsymbol{#1}", 1]
},
chtml: {
scale: 1.2
}
};
</script>
<style>
.MathJax, .mjx-math {
color: white;
}
</style>

View File

@@ -0,0 +1,7 @@
{{- $id := .Ordinal -}}
<span class="sidenote-container">
<a href="#sn-{{ $id }}" id="snref-{{ $id }}" class="sidenote-ref">[{{ $id }}]</a>
<span id="sn-{{ $id }}" class="sidenote">
<a href="#snref-{{ $id }}" class="sidenote-ref">[{{ $id }}]</a> {{ .Inner | markdownify }}
</span>
</span>

Binary file not shown.

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2023 YOUR_NAME_HERE
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.

View File

@@ -1,2 +0,0 @@
+++
+++

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
<div class="parallax background" style="overflow-y: none;"></div>
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>

View File

@@ -1,26 +0,0 @@
{{ define "main" }}
<div class="page-header">
<h1 data-value="Posts" class="typewriter scramble"></h1>
<p>Ideas, projects, and other musings.</p>
</div>
{{ range.Pages }}
<article style="margin-left: 10vw; margin-right: 10vw;">
<a href="{{ .Permalink }}">
{{ if .Params.banner }}
<div class="post-list-body-container">
{{ else }}
<div class="post-list-body-container full-width">
{{ end }}
<h4 data-value="{{ .Title }}" id="{{ .Title | anchorize }}" class="post-list-title">{{ .Title }}</h4>
<small class="post-list-date">{{ .Date.Format "January 2, 2006" }}</small>
<p>{{ .Summary }}</p>
</div>
{{ if .Params.banner }}
<div class="post-list-image-container">
<img src="{{ .Params.banner }}" alt="{{ .Title }}" class="post-list-image" />
</div>
{{ end }}
</a>
</article>
{{ end }}
{{ end }}

View File

@@ -1,11 +0,0 @@
{{ define "main" }}
<div class="page-header">
<h1 data-value="{{ .Title }}" class="scramble">{{ .Title }}</h1>
<small class="post-list-date">{{ .Date.Format "January 2, 2006" }}</small>
</div>
<article>
<div class="post clearfix">
{{ .Content }}
</div>
</article>
{{ end }}

View File

@@ -1,25 +0,0 @@
<footer>
<div class="footer-sections">
<div class="footer-list" style="width: 85%">
<h4 id="contact" class="footer-header" style="margin-bottom: 5px">Contact</h4>
{{ range .Site.Data.footer.Contact }}
<a data-value="{{ .name }}" class="scramble" href="{{ .url }}">{{ .name }}</a>
{{ end }}
</div>
<div class="footer-list" style="width: 85%"></div>
<div class="footer-list" style="width: 85%">
<h4 class="footer-header" style="margin-bottom: 5px">Services</h4>
{{ range .Site.Data.footer.Services }}
<a data-value="{{ .name }}" class="scramble" href="{{ .url }}">{{ .name }}</a>
{{ end }}
</div>
</div>
<p style="margin: 0px;">
<small>
Copyleft <span style="display: inline-block; transform: rotate(180deg);">&copy;</span> {{ now.Year }}.
<a style="font-size: 0.8em;" href="https://github.com/Clortox/tylerperkins.xyz">Free open source software.</a>
</small>
</p>
</footer>

View File

@@ -1,37 +0,0 @@
<head>
<link rel="stylesheet" href="{{ "css/styles.css" | relURL }}">
<link rel="stylesheet" href="{{ "css/header.css" | relURL }}">
<link rel="stylesheet" href="{{ "css/footer.css" | relURL }}">
<link rel="stylesheet" href="{{ "css/background.css" | relURL }}">
<script src="{{ "js/script.js" | relURL }}"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async src="https://ackee.clortox.com/tracker.js" data-ackee-server="https://ackee.clortox.com" data-ackee-domain-id="8e41f517-df44-41cb-970d-208872712ed6"></script>
{{ range .AlternativeOutputFormats -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
{{ if .Param "math" }}
{{ partialCached "math.html" . }}
{{ end }}
<
<meta name="description" content="Software Engineer, aspiring polymath"/>
<meta property="og:title" content="{{ if .Title }}{{ .Title }}{{ else }}Tylers Perkins - Software Engineer{{ end }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:image" content="https://opengraph.b-cdn.net/production/images/d8998659-0129-4808-b73c-67797d3fe7ac.jpg?token=X4ji3MDnTCq9oofsKJXYRPV4nIaynTNQmWu0UpE8vaE&height=460&width=460&expires=33281091161">
<meta property="og:description" content="Software Engineer, aspiring polymath" />
<meta property="og:site_name" content="Tylers Perkins" />
<meta property="og:locale" content="en_US" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="{{ if .Title }}{{ .Title }}{{ else }}Tylers Perkins - Software Engineer{{ end }}" />
<meta property="twitter:description" content="Software Engineer, aspiring polymath" />
<meta name="twitter:image" content="https://opengraph.b-cdn.net/production/images/d8998659-0129-4808-b73c-67797d3fe7ac.jpg?token=X4ji3MDnTCq9oofsKJXYRPV4nIaynTNQmWu0UpE8vaE&height=460&width=460&expires=33281091161">
<meta property="twitter:image:alt" content="Tylers Perkins Profile Picture">
<meta property="twitter:url" content="{{ .Permalink }}" />
<title>Tylers Perkins - Software Engineer</title>
</head>

View File

@@ -1,10 +0,0 @@
<header class="navbar sticky">
{{ range .Site.Data.header.LeftHandSide }}
<a data-value="{{ .name }}" class="scramble header-item" href="{{ .url | relURL }}">{{ .name }}</a>
{{ end }}
<div class="navbar-right">
{{ range .Site.Data.header.RightHandSide }}
<a data-value="{{ .name }}" class="scramble header-item" href="{{ .url }}">{{ .name }}</a>
{{ end }}
</div>
</header>

View File

@@ -1,105 +0,0 @@
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const typeWriterSpeed = 60;
window.onload = function() {
document.querySelectorAll(".scramble").forEach(item => {
item.addEventListener("mouseover", scramble);
});
window.addEventListener('scroll', function() {
let parallax = document.querySelector('.parallax')
let scrollPosition = window.pageYOffset;
let newSize = (document.querySelector('#content').offsetHeight * 0.8);
if(newSize < window.innerHeight)
newSize = window.innerHeight;
parallax.style.height = newSize + "px";
parallax.style.transform = 'translateY(' + scrollPosition * .5 + 'px)';
})
document.querySelectorAll(".typewriter").forEach(item => {
observer.observe(item);
});
}
function sleep(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
function replaceSpacesWithNbsp(string) {
if (string == null) return;
return string.replace(/ /g, "\xa0");
}
function scramble(event){
scrambleLetters(event.target);
}
function scrambleLetters(element) {
let iterations = 0;
const originalValue = element.dataset.value;
let interval = setInterval(() => {
element.innerText = element.innerText.split("")
.map((letter, index) => {
if(index < iterations){
return originalValue[index];
}
return letters[Math.floor(Math.random() * letters.length)];
}).join("")
iterations += 1 / 3;
if(iterations > originalValue.length){
clearInterval(interval);
element.innerText = originalValue;
}
}, 30);
}
async function typeWriter(element) {
let typeWriterText = element.getAttribute("data-value");
let output = element.innerText;
// write the main text
for(let i = 0; i < typeWriterText.length; ++i){
output += typeWriterText.charAt(i);
// sleep for typeWriterSpeed milliseconds
await sleep(typeWriterSpeed);
element.innerText = output;
}
//try and do the paragraphs associated
let hiddenElementId = element.getAttribute("data-show");
if(hiddenElementId == null) return;
let hiddenElement = document.getElementById(hiddenElementId);
let hiddenElementSpeed = 5;
let paragraphs = hiddenElement.querySelectorAll("p");
for (let p of paragraphs){
let hiddenElementText = p.getAttribute("data-value");
//hiddenElementText = replaceSpacesWithNbsp(hiddenElementText);
p.innerHtml = "";
output = "";
for (let i = 0; i < hiddenElementText.length; ++i){
output += hiddenElementText.charAt(i);
await sleep(hiddenElementSpeed);
p.innerText = output;
}
await sleep(50);
}
}
let observer = new IntersectionObserver(function(entries) {
for (let entry of entries){
// If the element is visible, start the animation
if(entry.isIntersecting) {
typeWriter(entry.target);
observer.unobserve(entry.target);
}
}
}, { threshold: 0.7 });

View File

@@ -1,21 +0,0 @@
# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "Clortox"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""