summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyren223 <contact@kyren.codes>2026-10-08 19:44:46 +0300
committerKyren223 <contact@kyren.codes>2026-10-08 19:44:46 +0300
commit9b03d2790b8b8db1b5a6755b6130f37e5f7f2455 (patch)
treea84f6e9cfe2b8aaf087d55830c938443f6be683e
parent6e70fe447ec9504757fbdc152b5b3697c9af8def (diff)
WIP
-rw-r--r--blogs/_degoogle-your-life.md31
-rw-r--r--blogs/_my-webdev-experience.md8
-rw-r--r--blogs/_rust-is-great.md101
-rw-r--r--blogs/_test.md37
-rw-r--r--blogs/discord-sucks-so-i-made-my-own.md186
-rw-r--r--blogs/how-computer-memory-actually-works.md198
-rw-r--r--blogs/hytale-modding-my-honest-concerns.md158
-rw-r--r--blogs/making-a-game-in-3-days.md78
-rw-r--r--blogs/nixos-the-perfect-distro-for-a-production-server.md284
-rw-r--r--blogs/the-search-for-the-perfect-ssh-key.md117
-rw-r--r--go.mod5
-rw-r--r--go.sum3
-rw-r--r--public/copy.js14
-rw-r--r--public/main.css5
-rw-r--r--public/my_uptime.js18
-rw-r--r--public/scrollbar.js52
-rw-r--r--public/style.css376
-rw-r--r--public/styles/terminal.css23
-rwxr-xr-xrun.sh21
-rw-r--r--website.go205
-rw-r--r--website.templ352
-rw-r--r--website_templ.go2014
22 files changed, 4128 insertions, 158 deletions
diff --git a/blogs/_degoogle-your-life.md b/blogs/_degoogle-your-life.md
new file mode 100644
index 0000000..d6fa8e1
--- /dev/null
+++ b/blogs/_degoogle-your-life.md
@@ -0,0 +1,31 @@
+---
+title: "How to de-google your life"
+description: How I de-googled my life and why you should do it too
+date: 2222-11-11
+---
+
+## What I already de-googled
+- Proton (mail, drive, calendar maybe?)
+- KeepassXC
+- Gitea
+- Linux
+- Firefox (zen)
+- DuckDuckGo
+- Google photos, Google Drive backup etc
+
+
+## New stuff
+- SMS "Messages" app
+- Kopia (write a password management/backup/other setup blog?)
+- F-droid
+- Use a custom android OS?
+- Control over notifications?
+- Fossify (Contacts/Phone/Messages)
+- CoMaps instead of Google maps
+- Aurora instead of Google play
+- NetGuard
+- Self host notes app? maybe just use git for sync + a markdown editor on mobile?
+- Disable "My files" "Usage access"
+- Disable "Phone Manager"
+- Weather app
+- Nova launcher
diff --git a/blogs/_my-webdev-experience.md b/blogs/_my-webdev-experience.md
new file mode 100644
index 0000000..6949572
--- /dev/null
+++ b/blogs/_my-webdev-experience.md
@@ -0,0 +1,8 @@
+---
+title: My webdev experience
+description: I made a website, here are my thoughts on web development
+date: 2024-12-13
+---
+
+Lorem ipsum dolor sit
+
diff --git a/blogs/_rust-is-great.md b/blogs/_rust-is-great.md
new file mode 100644
index 0000000..6916867
--- /dev/null
+++ b/blogs/_rust-is-great.md
@@ -0,0 +1,101 @@
+---
+title: "Rust is great... except for the language"
+description: ""
+date: 2026-08-27
+---
+
+For those who know me it might be weird seeing me say "Rust is great", after all, I have been
+openly bashing and criticize the language for quite a while now.
+
+While my opinion on Rust hasn't changed, I came to a realization that made me appreciate Rust
+for what it has taught me -- which I hope to share in this blog.
+
+## What is Rust?
+
+> Rust is a blazingly fast and memory-efficient language: with no runtime or garbage collector,
+> Rust's rich type system and ownership model guarantees memory-safety and thread-safety,
+> enabling you to eliminate many classes of bugs at compile-time.
+
+Well... at least that's how the Rust website describes the language, in reality this is mostly
+propaganda, it's inaccurate and misleading at best.
+
+A more fair definition might look like:
+
+Rust is a language that restricts the way you are allowed to program, enabling it to make certain
+guarantees that allows detecting memory and data corruption at compile time.
+Rust doesn't have a garbage collector, making it usually faster than garbage-collected languages.
+
+So, both of these definitions seem to focus on Rust being able to guarantees that usually only
+garbage collected languages can make, so how does it do that?
+
+Rust accomplishes that via it's most unique feature: the Ownership Model.
+
+## What is Ownership?
+
+There are 3 rules to ownership:
+
+- Each value in Rust has an owner
+- There can only be one owner at a time
+- When the owner goes out of scope, the value will be dropped (freed/deallocated)
+
+Assigning a value to a variable, passing it as an argument to a function[^1] or returning it will
+result in what's called a "move", where the existing variable gives up it's ownership and passes
+it to a new owner.
+
+This mostly applies to types that allocate a resource that needs freeing, which in Rust means any
+type that implements the `Drop` trait.
+Types that don't implement `Drop` such as integers and other primitives are "trivially copyable"
+meaning the value just gets copied instead of "moving" ownership.
+
+Just these rules already make a few nice guarantees:
+
+- Values are only dropped exactly once, preventing double frees
+- Values can't be used after being dropped, preventing use-after-free (UAF)
+- Values will be automatically dropped, preventing SOME memory leaks.
+
+One issue with this, is that if you pass a few values to a function temporarily, you must return
+all of them back, otherwise the caller can no longer use them.
+
+This is fixed by another concept called "borrowing" -- as the the name suggests, instead of giving
+up ownership, it allows to "borrow" a _reference_ to a value temporarily, and later return the
+reference back to owner.
+
+There are 2 types of references, a read-only (immutable) reference, and a read-write (mutable)
+reference. By restricting the programmer to only ever have a single mutable reference OR any
+number of immutable references Rust is able to guarantee a bit more:
+
+- Data races can't happen as a value will never be written to while also being read/written by
+ something else
+- References are always valid (no dangling references)
+
+There is one more concept worth mentioning, but first, I'd like to take this opportunity to shine
+a light on one of my biggest issues with the language -- the restrictiveness of it.
+
+While these rules do give us guarantees, they make our lives incredibly tedious, we constantly
+have to keep track of what's mutable, and what isn't, and what references do we have to what,
+and if we get any of that wrong, we have to stop what we are doing, read a long error message, fix
+the bug, and then go back and try to remember what we actually wanted to do - and then again,
+every few minutes, it destroys productivity.
+
+And it doesn't even need to be a complex example, even this simple code doesn't compile:
+```rust
+let my_list = vec![1, 2, 3, 4];
+let first: &i32 = my_list[0];
+my_list.push(5); // error: can't take mutable reference because immutable reference exists (first)
+let last: &i32 = my_list[my_list.len-1];
+println!("{first} and {second}");
+```
+
+This is a very simple code example that doesn't violate any of the guarantees[^2] Rust makes, yet
+Rust does not compile this code. This is because not every valid program would be verifiable by
+Rust, there will always be correct programs that Rust will deny.
+
+## Lifetimes
+
+
+
+
+
+
+
+
diff --git a/blogs/_test.md b/blogs/_test.md
new file mode 100644
index 0000000..344a479
--- /dev/null
+++ b/blogs/_test.md
@@ -0,0 +1,37 @@
+---
+title: Test
+description: This is a test blog to... well.. test things!
+date: 1970-01-01
+---
+
+## Secondary
+
+Testing some content with multiline thing
+let's see what happens if this wraps
+This should be super close
+
+This should be far
+
+- Some bullets
+- Adka kda kd ak
+- d kakd akd ka k
+
+## Smaller header
+
+How about code blocks
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello, World!")
+}
+```
+
+Hmm
+
+> Does this blockquote work
+
+This is `highlighting` I think is what it's called
diff --git a/blogs/discord-sucks-so-i-made-my-own.md b/blogs/discord-sucks-so-i-made-my-own.md
new file mode 100644
index 0000000..bfd4057
--- /dev/null
+++ b/blogs/discord-sucks-so-i-made-my-own.md
@@ -0,0 +1,186 @@
+---
+title: "Discord sucks! ...so I made my own"
+description: How I built Eko - THE discord alternative for terminal nerds
+date: 2025-08-08
+---
+
+## Discord sucks
+
+I have been disatisfied with discord for a few years by now, the main reasons that most people will
+agree with me, are it's slow, sluggish, bloated, and that it gets worse over time.
+
+- Reduced upload size from 50mb to 10mb
+- Ugly UI revamps
+- Literally ships with an entire browser (chromium due to electron)
+
+But there are a few more personal reasons for me.
+
+Ignoring the fact that I am in 100 servers and literally can't join any more,
+as someone who spends most of his day in the terminal, writing code with NVIM (btw),
+I really missed vim motions in discord, they are great and I'd probably talk about them in the future.
+
+## Searching for alternatives
+
+Vim motions are so ubiquitous they are practically everywhere, not just coding editors like Zed and
+vsc\*de but also note taking apps like Obsidian, and heck, even google docs have an extension for them.
+
+Surely an app as popular as Discord will have them right? well after some searching I found
+[Cordless](https://github.com/Bios-Marcel/cordless), a discord client in the terminal, great!
+
+Reading the readme, I immediately got dissapointed, the project has been shutdown.
+The reason was due to a high risk of Discord banning users for ToS violation, so I couldn't even make my own client!
+
+## The idea
+
+So I thought to myself, if Discord doesn't let me make a client, I'll write my own Discord, complete
+with a backend, frontend and most importantly, vim motions!
+
+Great, now that I have the idea, what languages do I use for the backend and the frontend?
+
+I heard that golang is a really good language for the backend, due to it's concurrency model making
+it easy to handle a ton of connections simultaneously.
+
+And what about the frontend? I'd need to find some library to help me make a TUI (terminal UI),
+After some searching, I found [bubbletea](https://github.com/charmbracelet/bubbletea) from [charm](https://github.com/charmbracelet),
+a golang library for creating TUIs, perfect.
+
+This has the added benefit of the backend and frontend being able to share types as they are
+written in the same language.
+
+## The implementation
+
+### The frontend
+
+Bubbletea uses the Elm architecture to create a UI, it is made out of 3 components:
+
+- The model - the state of the UI, it can contain UI elements, buttons, styles, and even other models
+- The view - a function that takes a model and returns the UI, in the case of a TUI, it just returns a string
+- The update - a function that takes the model and a message, and computes a new model based on that message
+
+The way it works, is a new message comes, for example a user resized the window, this message
+gets propagated to the model via the update method, which produces a new model, then
+the view method takes the new model and renders the new state to the UI.
+
+The concept itself is really great, although Bubbletea, partly due to golang had to make a few
+tradeoffs which made me like it less.
+
+First, golang doesn't have the concept of "const", so the view must take a copy of the model,
+FOR EVERY RE-RENDER, this is extremely inefficient, especially that most models can take thousands
+of bytes if not more, and that the copy is being pased on the stack.
+
+This doesn't only happen on view, this also happens in the update function, every update, the model
+also gets copied and passed on the stack, then the function modifies the stack version and returns
+a new version of it that is then also, gets copied on the stack.
+
+This is due to the Elm architecture originating from Elm, a functional programming language, in
+such languages mutating is discouraged, and everything gets copied.
+
+### The backend
+
+The server itself is fairly boring, it has a loop that accepts new TCP/TLS connections, and it
+spawns a new goroutine ("Green thread") to handle the client requests.
+
+So instead, let's talk about how IDs are handled!
+
+My first idea was to use a UUID (universally unique identifier), but it takes 128 bits, that seems
+way too much, 32 bits already allow for 4 billion permutations, but just in case (see IPv4 lol),
+64 bits should be plenty, that's 18 quintillion!
+
+For the format, is it fine to just randomly generate them? 18 quintillion is alot, but the chance
+of collissions is not as low as I'd like it to be, I wonder what Discord uses for their IDs...
+
+#### Snowflake IDs
+
+Discord uses snowflake IDs, originally invented by Twitter, they are 64-bit with a special format
+that guranteed uniqueness across multiple machines and data centers, here's how they work:
+
+```
+ 0 1 2 3 4 5 6
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|s| Milliseconds since Epoch | Node ID | Mac ID | Incremented |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+- s - The sign bit when storing the ID as an int64, unused (always 0)
+- Milliseconds since Epoch - The number of milliseconds since some date, in Twitter's case this is 2010, for Discord it's 2015, and for Eko it's 2025
+- Node ID - A unique identifier for the data center or cluster, in case of Eko this is always just 1 as I run eko on a single VPS server
+- Mac ID - The specific machine ID within a cluster, in Eko this is combined with the NodeID into a single 10-bit field, with a value of 1
+- Incremented - If 2 IDs are generated in the same millisecond, this is incremented, up to 4095 times,
+allowing a single machine to generate 4 million IDs per second (1000 * 4096)
+
+The snowflake ID also has the nice benefits of embedding a timestamp in it, this allows to save
+even more space by not needing to store a separate unix timestamp for creation dates, the creation
+date of a user is when their ID was first generated.
+
+If you are interested in knowing when your discord account was created, there are [websites](https://snowsta.mp/)
+that allow you to check this.
+
+Because everything in Eko uses snowflake IDs (users, networks, frequencies, messages) this trivially
+allows us to view the timestamp, the client just shifts 22 bits to get the milliseconds, adds the
+hardcoded Epoch, formats the unix timestamp, and now you got timestamps of messages.
+
+### The protocol
+
+Ok, so we have the backend, we have the frontend, but how will they communicate over the network?
+
+The first thought that came to mind, was to use whatever Discord uses, which is HTTPS and websockets,
+but this felt wrong, using such a bloated protocol just for sending a few text messages.
+
+I decided it'd be better to design my own protocol from scratch, that'd be efficient and small.
+After a while of planning, I came up with this packet format:
+
+```
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Version |En.| Type | Payload Length |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Payload... Payload Length bytes ... |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+```
+
+- Version (8 bits) - the most important thing when designing a protocol is to have a version field,
+ so that when you inevitably introduce breaking changes, the server and client will notice a mismatch
+ and know how to deal with it
+- Encoding (2 bits) - Indicates the format of the payload, more on that later
+- Type (6 bits) - The type of the payload, what kind of message it is
+- Payload Length (16 bits) - How long is the payload, 16 bits gives us a maximum payload size of 65,536 bytes
+- Payload (0-65k bytes) - The actual body of the message, formatted per the encoding
+
+I was thinking about how to encode the payload in an efficient format, JSON was just too verbose
+for this, after taking a look at protobufs, cap'n proto and MsgPack, I chose MsgPack, mostly
+because of it's ease of use while being almost as fast as the other 2.
+
+But I still wanted to support JSON, so I introduced a 2-bit encoding into my packet,
+the idea is the packet sender specifies 0 for JSON, 1 for MsgPack, and the other 2 are reserved.
+That way the server and client can communicate with MsgPack, but if a developer writes their own
+client that uses JSON, the server can handle that just as fine.
+
+Another thing to mention is how the type thing is used as a "tagged union", it specifies the
+structure of the payload, unlike a tagged union though, it doesn't need to store the size of the
+largest packet, that's what the payload length field is for.
+
+## The release
+
+After months of work, I am happy to present...
+
+[Eko](https://github.com/kyren223/eko) - a terminal-native discord alternative for terminal enthusiasts!
+
+<img
+ src="/eko-screenshot.png"
+ alt="Description"
+ style={{ width: "1000px", paddingBottom: "20px" }}
+/>
+
+For more information head over to the [README](https://github.com/kyren223/eko), or install it now with
+
+```
+curl -sS https://eko.kyren.codes/install.sh | sh
+```
+
+Hope to see you there!
+
+As always if you have questions or want to chat, feel free to contact me on discord at Kyren223 or email me at `contact at kyren.codes`.
+
+You can also add me on Eko, here's my ID: `2102554263552`
diff --git a/blogs/how-computer-memory-actually-works.md b/blogs/how-computer-memory-actually-works.md
new file mode 100644
index 0000000..42a4acc
--- /dev/null
+++ b/blogs/how-computer-memory-actually-works.md
@@ -0,0 +1,198 @@
+---
+title: How computer memory ACTUALLY works
+description: Virtual address space, paging, the stack and the heap.
+date: 2025-09-25
+---
+
+## How memory is usually taught
+
+Most universities, schools and online courses usually separate memory into 2 places:
+
+- The stack, where all your local variables live.
+- The heap, the magical place where you go get memory if you don't know the size ahead of time.
+
+This is a bit inaccurate, the CPU doesn't really differentiate between the stack and the heap[^1],
+For the CPU, it's all just a bunch of memory.
+
+## Virtual Memory
+
+In the past, programs would use physical addresses to refer to a specific place in the computer's RAM.
+nowadays this is no longer the case, because of several issues:
+
+- Insecure - malicious programs can read or write into another program's memory which may contain passwords or other secrets.
+- Bug prone - a program may accidentally read or write into arbitrary places, causing the entire system to crash
+ or misbehave, rather than just the program itself.
+- RAM-limited - the maximum memory limit is how much RAM the computer has, saving and
+ restoring memory from disk is not possible.
+
+The way modern CPUs and operating systems solve this problem is by assigning every process (program)
+it's own "virtual address space" and then map virtual addresses into physical addresses.
+
+- Fixes security - each process has it's own address space, and can't access the other address spaces.
+- Fixes bugs - the process is self-contained, crashing doesn't effect other processes.
+- Fixes memory limits - the operating system can intervene and swap memory in and out of disk
+ to emulate more RAM (at the cost of lower speeds).
+
+So how does this work?
+
+On 64-bit operating systems, pointers (addresses) are stored as 64-bit numbers, this allows for
+2^64 addreses, which is excessive, in reality, CPUs only use 48 of those bits[^2] to store the address,
+this means the virtual address space is 256TB in size.
+
+```
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| ignored | 48-bit virtual address |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+Of course, most computers don't have 256TB of physical memory[^2], for example, I only have 64GB.
+
+The reason this works is **virtual memory may not have physical memory backing it**, and keeping
+track of virtual memory is essentially free[^3].
+You are not storing 256TB of data, you only need to keep track of ranges of virtual addresses and
+their mapping to the physical addresses that back them up.
+
+For performance reasons, the mappings don't store ranges of individual bytes of memory, but rather
+chunks, usually 4k bytes, this is known as a "page".
+
+The operating system sets up a "page table" for each process which maps virtual pages in the process
+virtual address space into physical pages in the computer's RAM.
+
+This is enabled by hardware support. CPUs understand what a "page table" is, and have a special
+register that points to a page table, the operating system then makes sure to set that register
+to the correct page table for the currently running process.
+
+Each entry in the page table has some additional metadata such as:
+
+- Read access bit - whether the process is allowed to read from the page.
+- Write access bit - whether the process is allowed to write to the page.
+- Execute access bit - whether the process can execute code that is inside the page.
+
+We will see why that's useful soon.
+
+## Using Virtual Memory
+
+Operating systems provide through syscalls 4 main operations for dealing with virtual memory:
+
+### Reserve
+
+You ask the operating system to reserve a certain amount of virtual address (must be a multiply of
+the page size), and it gives you back a pointer to the first address.
+
+Reserving only guarantees that if you ask again, it won't give you that same range of addresses,
+but it does not yet ask for physical memory so this operation is essentially free.
+
+Trying to access reserved pages will result in a page fault causing the operating system to
+terminate the proggram with a segmentation fault (segfault).
+
+### Commit
+
+Similar to reserve, but now you are asking the operating system to back up the virtual pages you asked for with physical pages.
+You are allowed to commit pages that you have previously reserved.
+
+The operating system may not back the pages yet with physical pages, but mark the page as "no access",
+as in read, write and execute bits all set to 0.
+
+Trying to access the pages, triggers a page fault, waking up the OS, which checks if the page was commited,
+and if it was, instead of crashing the program, it'd back up the virtual page with a physical page
+(potentially swapping a page from disk), and then resuming the program.
+
+From the program's perspective, everything looks normal except the performance hit due
+to the added delay, which can be measured.
+
+### De-commit
+
+De-commit releases the physical memory backing the page up, and changes the virtual page to being reserved.
+
+### Release
+
+Release releases physical memory of the page AND un-reserves it from the virtual address space.
+
+## What is the stack VS the heap then?
+
+So, if the operating system only deals with pages and virtual memory, then why do people
+differentiate memory into "the stack" and "the heap"?
+
+### The stack
+
+The stack is a region where functions[^4] store their local variables, but it's just
+memory, the compiler specifies the wanted stack size in the executable format (usually 1MB on windows and 8MB on linux),
+and the OS reserves it in the virtual address space when it creates the process.
+
+The heap on the other hand, is a higher-level interface that operating systems usually provide.
+it's a way to allow many big or small allocations, to be as efficient as possible.
+To achieve this, it uses techniques like freelists to chunk many pages into fine-grained allocations.
+
+### Allocators
+
+Things like `malloc` and `free` are examples of heap allocators.
+
+In almost all cases, you don't need such a generic allocator like malloc, and instead, other allocators
+such as arenas and pools cover most use cases.
+
+Arenas are stack allocators, they have a base pointer and a size, and they can push new elements,
+pop the last element, or release the entire thing at once (by resetting to 0).
+
+Use cases for arenas include in games or apps, a frame-arena that you reset at the start of a frame,
+or in web servers a per-request arena that resets when a response is served.
+
+A pool is an arena that stores fixed-size elements, this restriction enables the ability to reuse
+elements that have been freed anywhere, not just the top of the stack like a normal arena.
+
+Use cases for pools are for example in games, a pool for enemies, defeating an enmey and then
+spawning a new one, reuses the space left by the defeated enemy.
+
+For more information on memory management techniques, see [my curated list of resources for managing memory](https://git.kyren.codes/Kyren223/resources#how-to-actually-manage-memory-its-easy).
+
+## A cool trick that virtual memory enables
+
+### Growing a Dynamic array / Vector / List without resizing
+
+The way most languages implement dynamic arrays (like C++, Rust, Go) force a limitation on the programmer:
+pointers are not stable.
+
+This means that if you take a reference to an element in the dynamic array, and then add an element to it,
+the reference may no longer be valid.
+
+This is due to the case where you add an element, if there is not enough space in the array,
+it may need to resize it, by creating a new (larger) array, copying all the elements, and then
+returning the new larger array, so of course retaining a reference to the elements isn't valid.
+
+This annoying restriction can be lifted entirely by using the knowledge we learned about how
+the virtual address space works.
+
+Because the address space is so large (256TB), we can reserve an excessively large chunk, say 64GB
+of it, but only commit a single page, then whenever we need to grow, we just commit more pages.
+
+Of course, 64GB is quite large and takes a decent chunk out of those 256TB, but the point was to
+show it's possible. In practice, you will most likely have some upper bound for how much you are
+expecting to be the maximum, for example in my own codebase, I usually only reserve 64MB.
+
+This works because from the perspective of your program, addresses are contiguous due to using
+virtual memory. The physical pages in RAM that back the virtual memory are basically always
+scattered and fragmented anyways.
+
+## End
+
+As always if you have questions or want to chat, feel free to contact me on discord at Kyren223 or email me at `contact at kyren.codes`.
+
+And if this kind of stuff interests you, I recommend checking out [my curated list of resources](https://git.kyren.codes/Kyren223/resources)
+for further reading about these topics.
+
+[^1]:
+ CPUs have 2 registers usually called sp (stack pointer) and bp (base pointer) for
+ storing the lower and upper bounds of the currently active stack.
+ They are used in instructions like push and pop.
+
+[^2]: Some super computers exceeded the 256TB limit, so certain CPUs support 52-bit or 56-bit addresses
+
+[^3]:
+ Misses in the translation lookaside buffer (a cache on the CPU for translating between virtual and physical memory)
+ can sometimes become a performance bottleneck (Thanks Martins Mozeiko for pointing this out!).
+ This is due to the need of storing an entry for every 4kb page, this can be mitigated by using
+ "large pages", which are usually 2MB in size, substantially reducing the amount of needed page entries.
+
+[^4]:
+ Procedures is the more correct term, function implies "pure function" with no side effects (like in math),
+ while procedures may or may not have side effects.
diff --git a/blogs/hytale-modding-my-honest-concerns.md b/blogs/hytale-modding-my-honest-concerns.md
new file mode 100644
index 0000000..bf74ab9
--- /dev/null
+++ b/blogs/hytale-modding-my-honest-concerns.md
@@ -0,0 +1,158 @@
+---
+title: Hytale Modding - My honest concerns
+description: Hytale finally released, promising modding, but iz it true?
+date: 2026-02-02
+---
+
+## Hytale
+
+For those who don't know, [Hytale](https://hytale.com) is a video game created by Hypixel Studios (the people behind Minecraft's largest server: Hypixel), which has been in development for over 7 years, and has finally been released in public beta on January 13th 2026.
+
+Minecraft was a big part of childhood, and was what pushed me into becoming a programmer, I started by making Minecraft mods and plugins using Java.
+So you can imagine how excited I was for this new game to release, especially because of the promises around great modding support.
+
+Unfortunately, now that the game has released, and we can see how the game and it's modding support works, I have came to realize a few major problems with the way it's been designed, and unless these problems are addressed and fixed, I am unable to imagine a thriving modding community at the level Minecraft has.
+
+## The good
+
+First, to not be all that negative, I have to give credit to Hypixel Studios for the things they did get right when designing the game and the way modding works.
+
+### The Entity Component System
+
+An Entity Component System (ECS) is a somewhat old concept that has only recently became popular in the game development world, it's a way to architecture the game by composing entities out of various components, and then running systems to update components efficiently.
+This is opposed to the more common approach (that Minecraft uses) which is Object-oriented programming (OOP) which creates hierarchies of entities, leading to less modularity and horrible performance.
+
+Although Hytale's ECS is written in Java, which prevents most performance optimizations, it's still way better than OOP, both in terms of performance and in terms of developer experience.
+
+### Official modding
+
+Unlike Minecraft which has multiple 3rd-party providers for modding (Forge, Fabric, Spigot, and more), Hytale has a single official API.
+
+This leads to 2 main benefits: users don't have to choose which provider to use (limiting them to only mods written for that provider) and developers don't have to port their mods to multiple providers to be more widely available.
+
+## The bad
+
+I do hope Hytale succeeds, and it is a beta after all so things aren't perfect, but my concern is their next plans for modding seem to focus on less relevant things rather than solving the major issues.
+
+### No client modding
+
+Hytale modding is completely server-side, similar to Minecraft plugins or server-side mods.
+
+This means that a mod's code is run solely on the server and not on the client's computer, while assets such as textures, models, sounds and configs are sent to the client to be used.
+
+I think the logic behind this is to allow Hytale to release the original unobfuscated server source code (which Minecraft only recently done), but disallowing in their ToS any client-side modifications and reverse engineering, to still keep half of their game "private".
+
+### Only one version
+
+Hytale forces the client to always use the latest version, unlike Minecraft which allows going back to older versions.
+
+Their logic is that server owners and modders only need to take into consideration the latest version of the client, which saves time.
+
+## The ugly
+
+At first glance, these decisions seem good and beneficial, but upon further consideration, they are actually detrimental to a thriving modding community.
+
+### High latency lag
+
+The largest issue with everything being on the server, is every time the client wants to do something, it must do a roundtrip by sending a request to the server and awaiting for a response, this is what ping is, how long it takes for a roundtrip.
+
+My average ping on Minecraft is 200-300ms, that's 1/4 of a second or equivalent to 4fps, imagine how annoying it'd be to have to wait that long between pressing a button to move, to actually moving on the screen, it'd be unplayable.
+
+Luckily there is a solution for this -- prediction (of movement), the client AND the server both calculate the location, then, the client moves there on the screen immediately, without waiting for the server.
+Then once the server responds, the client checks if the results are too much out of sync, in which case it uses the server position.
+
+Fortunately both Minecraft and Hytale implement this (if you ever got "lag backed", this is why).
+
+But unlike Minecraft which does it for movement, blocks, chests, and more, Hytale does it to a more limited set of hardcoded things like movement, but not blocks and UIs.
+
+Usually this wouldn't be an issue, mod creators can fix this by creating their own mod that runs on both the client and server, where the client version does the prediction, making it feel instant, unfortunately this is impossible with Hytale, this means that mods like Refined Storage will have a 1/4 of a second delay each time you try to put/take a block from the inventory, very annoying.
+
+Another limitation of no client side modding support is the inability of adding things like custom key bindings, this can be added by Hytale, but again, with a delay due to server roundtrip latency.
+
+### Per-clieant customizations
+
+Minecraft has plenty of client-side mods which allow customizing the client in a way that doesn't effect the server, this is most often used for visual effects using resource packs, shaders or additional HUD elements.
+
+This is unfortunately impossible in Hytale beyond the few hardcoded settings that exist, due to mods being server side only, it means that at best servers can control a single global texture pack for all players that play it, but there is no individual personalized resource packs, and shaders are completely impossible.
+
+Even community-made client-side mods by reverse engineering the client can't exist because Hytale explicitly prohibited it in their ToS and made it illegal to do.
+
+### Version chasing
+
+In practice, rather than helping players and modders save time, targeting only one client version causes a huge problem.
+
+Players can't play servers that don't support the latest version of the client, If there is a breaking change to the client-server protocol, which will almost certainly happen every large version update, then players won't be able to play.
+
+In practice this means that Hytale not only forces clients to not play older versions, they also force server owners to always run and update to the latest server version, which forces modders to update their mods to the latest version.
+
+This is a huge problem for a bunch of reasons:
+
+Minecraft mods/modpacks usually target every other major version, this gives mods more time to improve their mods before immediately having to work on porting to the new versions.
+
+Mods that were abandoned and not updated to newer versions will not be playable, which is a huge issue considering the most played Minecraft modpacks (SkyFactory, GregTech, FTB) use older Minecraft versions, and have not been updated to the latest one.
+
+Having access to older versions means that if Hytale ever makes a change the community dislikes, they can still go back and play older versions they enjoy more, similar to how some Minecraft players still play 1.8 because they prefer that PvP style.
+
+### Lack of Big mods & modpacks
+
+All the previous issues will likely lead to this problem, there won't be many competent developers willing to invest massive amounts of time to make high quality mods for the community.
+
+And I don't blame them, why would you spend so much time if you have to constantly update your mods to newer versions instead of improving them, have your mods feel laggy due to high latency and have players forget them if you ever stop updating them to the latest version, and all of that, for free.
+You may as well take that effort and put it into Minecraft modding.
+
+There will certainly be attempts to replicate mods like RS/AE2, Create, Mekanism,
+Botania and others, but they will be made by less experienced beginners and will probably be inefficient and worse than their Minecraft equivalents.
+
+### No content for players
+
+Hytale is still in beta, and doesn't have much content yet, it only took me a few days (20-30 hours of playtime) to explore about 75% of the content (on my first and casual playthrough).
+
+Usually this isn't a problem, except that Hytale's Community are mostly Minecraft players, the same people who expect endless content for a $20 game.
+
+There is a reason I was able to play Minecraft almost daily for an entire decade without getting bored, and that reason is mods.
+
+The base game eventually gets boring, there are only so many things you can do, and Hytale knows it, which is why they advertise their "Great modding capabilities" so much, they are counting on modders creating content for them for players to play.
+
+If there are no competent modders to create popular high quality mods, then there is no content, if there is no content, players will eventually get bored and move on to other games, no players, means the game will die.
+
+I really hope this doesn't happen, but at the current state of things, I can't see how it won't happen.
+
+## Solutions
+
+Let's end this on a more positive note -- what can Hytale do to fix these issues and convince modders it's worth their time?
+
+### Multiple versions
+
+Let's start with the easy problem - only one version, the fix is extremely simple, just add a dropdown in the launcher to select previous client versions.
+Of course, I don't expect Hytale to support all these versions, by neither does Minecraft, just have them available to download, and even add a "Unsupported" text next to them.
+An even nicer option would be to allow separate game profiles which contain both versions, world and settings, but this can be left as a responsibility for 3rd-party launchers (like Modrinth, and Prism in Minecraft).
+
+### Client-side modding
+
+For the other problem of client side modding, it's a bit more complicated and technical but still doable.
+
+Hytale seems to have 2 main requirements that prevented it from doing client-side modding like Minecraft: security and copyright/IP.
+
+Minecraft client mods are inherently insecure, they have access to the client's computer, allowing malicious mods like rats, ransomware and otherwise to effect your system.
+
+So how do we allow code execution, without opening ourselves to security, while also not exposing any details about the client code to avoid copyright/IP issues?
+
+It's simple! Just copy what websites do!
+Websites also run code on a User's computer, and yet if you go to a malicious website, they can't hack you, the solution is by running a sandboxed programming language and then only allowing it to access an API, which decides what it is or isn't allowed to do.
+In the web world, this is done using JavaScript and web APIs.
+
+JavaScript is terribly designed, so Hytale can use something else like Lua, then Hytale needs to write a comprehensive API allowing lua code to access all game related values on the client (player position, entities, chunks UIs, assets, etc).
+
+This solves the security issues, lua is sandboxed and thus any malicious code can't do anything harmful, and because they only expose things via an API, Hytale doesn't have to share the client's source code.
+
+Then, to fix both server-side latency and client-side customization: Lua scripts can be sent to the client the same way assets like textures are currently sent, this allows server-side mods to write their own client-side predictions avoiding latency, or alternatively, players can install scripts locally to only modify their client without effecting the server.
+
+And when Hytale was owned by Riot games (before Hytale was purchased back by it's original founder Simon), they were planning on using Lua for what seemed like a very similar system to what I was proposing.
+
+But unfortunately instead of working on that, Hytale seem to want to focus on a very complex visual scripting system for client side modding, which not only will take significantly longer to implement than what I proposed, it'd also be significantly limited and most programmers are more productive and prefer writing code in text rather than a visual scripting system.
+
+## Conclusions
+
+Hytale seems like a very promising game that can finally give a new fresh take on the Minecraft genre, but in its current state it has major flaws in terms of modding ability, but I believe it can be resolved by implementing my suggested solutions.
+
+And as always, if you wish to talk to me about this topic, programming or anything else, you can contact me on discord at Kyren223 or email `contact @ kyren dot codes`.
diff --git a/blogs/making-a-game-in-3-days.md b/blogs/making-a-game-in-3-days.md
new file mode 100644
index 0000000..47b0949
--- /dev/null
+++ b/blogs/making-a-game-in-3-days.md
@@ -0,0 +1,78 @@
+---
+title: "Making a game in 3 days"
+description: How we built a game in zig for the boot.dev hackathon
+date: 2025-07-28
+---
+
+This blog post is about my experience participating in the boot.dev 2025 hackathon.
+
+## The team
+
+- Kyren223 (me) - discord kyren223
+- GottZ - website https://contact.gottz.de/
+- Nuclear Pasta - discord jacob_v_thaumiel
+
+## The idea
+
+Initially we wanted to make a programming language REPL, but a REPL is not very
+visual, and we also realized that maybe making an entire language in 3 days
+isn't very realistic, so instead we decided to go for an even more difficult
+idea, making an entire game, in 3 days...
+
+## CPU vs AI
+
+We knew we wanted to make something coding-related, we thought about including
+a programming language in the game, and that led us to an idea about CPU instructions.
+This idea evolved into a tower defense game, where the CPUs are towers defending
+against pesky bugs like null pointer dereferences and infinite loops.
+
+For the language, we chose Zig, for the following reasons:
+
+- Me and nuc are already familiar with it (and really like it)
+- Total control over memory (manual memory management)
+- Blazingly fast
+- Great interopability with C (for GottZ)
+- Simple and C-like
+- Modern (optionals, tagged unions, functions on structs, etc)
+
+For the game library, we decided to go with [raylib-zig](https://github.com/Not-Nik/raylib-zig),
+the [raylib](https://github.com/raysan5/raylib) bindings for Zig.
+
+We did so because raylib is an extremely simple to use bare bones library
+to handle things usch as player input and graphics.
+
+## Difficulties
+
+To avoid git conflicts on a fresh project, we decided to use [Zed](https://zed.dev/)'s collaborative features
+to write code as if it was a google doc.
+
+This worked for a while but the Zig LSP didn't work well and it was really hard
+to test when only 1 person can build the project.
+
+Because of this on the 2nd day once we had more "surface area" we decided to
+go back into commit-push and pull workflow so each of us can use our favorite
+editor and run the project locally.
+
+By the end of the 2nd day the project was mostly complete, we spent the rest of
+the day adding polish like fancy game over and victory screens, statistics,
+making the readme, fixing bugs etc.
+
+## Finally
+
+After 3 days of hard non-stop coding, we actually got a working game.
+It consists of 3 levels and even with very basic mechanics it's already fun!
+
+> Nuclear Pasta
+> I feel like I'm playing Bloons
+
+So what are you waiting for? go give the game a try!
+
+[https://github.com/The-Memory-Managers/cpu-vs-ai](https://github.com/The-Memory-Managers/cpu-vs-ai)
+
+Don't forget to star the repo!
+
+Not in the mood to play? watch a demo playthrough [here](https://youtu.be/_clLDGNlef0).
+
+This is just a prototype, we plan on (eventually TM) making a full game out of it, so stay tuned!
+
+As always if you have questions or want to chat, feel free to contact me on discord at Kyren223 or email me at `contact at kyren.codes`.
diff --git a/blogs/nixos-the-perfect-distro-for-a-production-server.md b/blogs/nixos-the-perfect-distro-for-a-production-server.md
new file mode 100644
index 0000000..7b8c235
--- /dev/null
+++ b/blogs/nixos-the-perfect-distro-for-a-production-server.md
@@ -0,0 +1,284 @@
+---
+title: "NixOS: The perfect distro for a production server"
+description: My experience using NixOS to manage my production VPS
+date: 2025-01-01
+---
+
+NixOS is a linux distribution praised for it's functional-style declarative
+configuration that allows you to manage the entire system through the Nix
+programming language, making your sytem reproducible and more reliable.
+
+This is in contrast to other linux distros that use a more imperative approach
+of managing the system by running commands to install packages, create
+users, etc.
+
+## NixOS as a daily-driver operating system
+
+About half a year ago I started using Windows subsystem for linux which enabled
+me to run a linux distro in the terminal, 6 weeks ago I finally decided to make
+the jump and switch from windows to a full linux desktop experience.
+
+I wanted a linux distro with a large set of latest-version packages but still
+stable without risk of getting my system broken (sorry Arch).
+
+I was considering going with Tumbleweed, a rolling release distro
+by openSUSE which I was already familiar with because I was using it in WSL.
+
+But then I remembered Nix, this weird thing that is an operating system,
+programming language, and a package manager.
+
+Nixpkgs is the the package repository with the most fresh packages (even more than AUR),
+and it has a really cool benefit of being able to install all my packages
+through a config file, similar to NVIM with lazy.nvim or rust with cargo.
+
+Initially I was considering using something like Debian with nixpkgs as I heard
+that was possible, but some people warned me that it's a bad idea to not use
+the native package manager, so I ended up **going with NixOS as my first linux
+distro on desktop**.
+
+I followed a [great video about installing NixOS](https://www.youtube.com/watch?v=a67Sv4Mbxmc) by Vimjoyer
+and was able to get it up and running.
+Although I experienced some issues in the first few days, that was expected
+and I was able to solve all of them.
+
+One trap to be aware of is the nix community is sometimes too enthusiastic and
+will try to convince you to do things "the nix way" like rewriting your
+perfectly fine existing configs in nix or moving from stow to home manager.
+
+In my opinion, I don't want to waste so much time configuring everything,
+I am OK with only configuring system settings and packages while having things
+like other dotfiles or scripts be handled the "normal linux way".
+
+## NixOS as a production server distro
+
+A few days ago I got my first VPS, a RS 1000 from netcup (use code
+`36nc17355743780` for a $5 discount),
+my goal with it was to host my website along with some other self-hosted apps
+including my own server for a terminal-based social media I am working on
+(which I will make a blog post about in the future).
+
+Infrastructure as code (IaC) is the practice of managing servers through
+configuration files rather than manual instructions (such as running commands),
+this has several benefits such as being able to use version control and ensuring
+a more reproducible server that can be easily deployed and updated using these
+files.
+
+This is where NixOS comes in and really shines, all these "nix ways" of
+configuring stuff as a desktop OS seems overkill and time consuming but for a
+server that's exactly the point, to be able to reproduce the entire system
+from only a bunch of nix files.
+
+## Configuring a server with NixOS
+
+After struggling to install NixOS, I discovered [NixOS Anywhere](https://github.com/nix-community/nixos-anywhere)
+which made it very easy to install remotely through SSH.
+
+First thing was setting up proper SSH authentication and security.
+
+Nix makes it really simple to do, just add these 2 lines to enable ssh
+but disable the insecure password authentication
+
+```nix
+services.openssh.enable = true;
+services.openssh.settings.PasswordAuthentication = false;
+```
+
+And to add a public key to connect with to the root user
+
+```nix
+users.users.root.openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7P9K9D5RkBk+JCRRS6AtHuTAc6cRpXfRfRMg/Kyren"
+];
+```
+
+If you would like to know how I generated a key that ends with `/Kyren` read
+my first blog post - [The search for the perfect ssh key](http://localhost:4321/blogs/the-search-for-the-perfect-ssh-key)
+
+### Managing secrets with sops-nix
+
+Next, before we can get into configuring a webserver, we need a good way
+to store secrets for things like SSL certifications, [sops-nix](https://github.com/Mic92/sops-nix) is a nix module
+that integrates the [sops CLI tool](https://github.com/getsops/sops) into nix.
+
+Rather than having to set up 20 different secrets for each thing, it allows
+us only setup 1 secret that is stored on the server and on the admin's
+system, then, any additional secret can be defined in a file that gets
+encrypted automatically by sops which can safely be committed into version control.
+
+At runtime sops will write all these files to a directory which can then be
+accessed by anything that needs access to that secret, sops also makes it easy
+to let specific users or groups access to a specific secret.
+
+### Setting up automatic updates
+
+We have added sops-nix, but how can we get it into the server?
+
+The simple approach would be to ssh as root into the server, git clone the
+repository with the config and then run
+
+```sh
+nixos-rebuild switch --flake .#default
+```
+
+_assuming you are inside the git directory where your `flake.nix` is and your
+`nixosConfigurations` is called `default`_
+
+While that certainly works, a better approach would be to listen for changes
+to the git repository and automatically pull and rebuild the system.
+
+```nix
+system.autoUpgrade = {
+ enable = true;
+ flake = "github:kyren223/server#default";
+ dates = "minutely"; # Poll interval
+ flags = [
+ "--no-write-lock-file" # Prevent flake.lock from upgrading
+ "--option" "tarball-ttl" "0" # Required for polling below 1h
+ ];
+};
+```
+
+This will automatically run every minute to fetch the github repository
+and rebuild the system.
+
+But wait, what if the repository is private? that's where sops-nix comes in!
+
+```nix
+sops.secrets.github-access-token = { };
+nix.extraOptions = "!include /run/secrets/github-access-token";
+```
+
+The first line defines a new `github-access-token` secret, and the second line
+makes sure nix is aware of it so it can use it when trying to fetch the repo.
+
+Of course, make sure to define the secret in your `secrets.yml` file,
+The key would be `gituhb-access-token` and the value would be a github PAT
+token.
+It's recommended to only give the token read-only access to the repo's contents.
+
+## Serving a static website
+
+Now that we have a way to manage our secrets, and continious deployment of our
+nix configuration, we can finally start working on a website.
+
+The plan is to host multiple websites in the future so I'll be using [Nginx](https://nginx.org/en/)
+as a reverse proxy.
+
+```nix
+services.nginx.enable = true;
+services.nginx.virtualHosts."kyren.codes" = {
+ useACMEHost = "kyren.codes";
+ forceSSL = true;
+ locations."/" = {
+ index = "index.html";
+ root = "/srv/website";
+ };
+};
+```
+
+This enables nginx, which installs it on our system then defines a new virtual
+host with the domain `kyren.codes`, later if we were to host other sites, the
+virtualHost can be something like `git.kyren.codes`.
+
+The `locations."/"` sets the base url of our website to `/srv/website` which is
+where I have my website's static files, and nginx will serve `index.html` as
+the default page when you go to that domain, later I will explain how to setup
+continious deployment to automatically update this directory on push to master.
+
+The `useACMEHost` and `forceSSL` makes sure the server uses https, you'd need
+to get a SSL certificate from [Let's Encrypt](https://letsencrypt.org/), once
+again, Nix makes it really simple to do
+
+```nix
+# Define the token using nix, make sure the "acme" user can access it
+sops.secrets.cloudflare-dns-api-token = { mode = "0440"; owner = "acme"; };
+
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "acme@kyren.codes"; # Your email
+
+security.acme.certs."kyren.codes" = {
+ domain = "kyren.codes";
+ extraDomainNames = [ "*.kyren.codes" ];
+ dnsProvider = "cloudflare";
+ environmentFile = "${pkgs.writeText "cf-creds" ''
+ CF_DNS_API_TOKEN_FILE=${config.sops.secrets.cloudflare-dns-api-token.path}
+ ''}";
+ webroot = null;
+};
+
+# Allow nginx to access acme certs
+users.users.nginx.extraGroups = [ "acme" ];
+```
+
+This defines a `kyren.codes` entry (which is what `useACMEHost` references)
+I am using cloudflare as my DNS provider, but you can get a full list of
+supported DNS providers at [this link](https://go-acme.github.io/lego/dns/), and
+of course make sure to change the environment variables to match your provider.
+
+Now if we commit and push these changes and wait about a minute for the server
+to pull the changes, we should have a working static site at `kyren.codes`.
+
+### Setting up continious deployment for the website
+
+The way we are going to do this on the server is create a new linux user and
+add to it a public ssh key that will be used for deployments.
+
+It's recommended that the user is restricted as much as possible so in the case
+of a hacker getting access to the key, they won't be able to get into other
+services that are running on the server.
+
+We can create a user declaratively like this
+
+```nix
+users.users.website = {
+ isNormalUser = true;
+ group = "users";
+ openssh.authorizedKeys.keys = [
+ # Allow an admin to manually SSH
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7P9K9D5RkBk+JCRRS6AtHuTAc6cRpXfRfRMg/Kyren"
+
+ # The deployment public key
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ1B/i/AQLYt6mrz0P/oUJItpvWXp7z0xHNzmcPdtwWd"
+ ];
+};
+
+# Make sure the /srv/website always exists
+# Give read and write permissions to the website user
+# Give read permissions to the nginx group (so nginx can serve it)
+systemd.tmpfiles.rules = [
+ "d /srv/website 0750 website nginx"
+];
+```
+
+Now all you need to do is have a github action (or similar) that builds your
+website then copies the build output into the server by SSH-ing into it.
+You can take a look at [my github action](https://github.com/Kyren223/website/blob/master/.github/workflows/deploy.yml)
+for my website to see an example of how you can do it.
+
+## Conclusions
+
+NixOS helps system admins and developers achieve infrastructure as code by
+allowing them to declare the entire machine's configuration using the powerful
+Nix programming language.
+
+This makes the state of production machines more consistent and reliable while
+making it easy to reproduce across hundreds of machines.
+
+Compared with the traditional approach of a debian or ubuntu server, where
+system admins would have to painfully manage the state of all machines ensuring
+all the dependencies, firewalls, OS settings, etc are configured properly.
+
+Because of the difficulty, OCI containers such as docker became popular, but
+they come with a cost, mainly performane due to virtualization. NixOS makes it
+easy to configure programs to run on bare-metal making it a better, more
+performant alternative to docker.
+
+**Thanks for reading! wishing you a year full of code, growth and new oppurtunities!**
+
+If you'd like to learn more about NixOS, here are some resources I found useful:
+
+- [NixOS in production](https://github.com/Gabriella439/nixos-in-production)
+- [Vimjoyer's Youtube Channel](https://www.youtube.com/@vimjoyer/videos)
+
+Have questions or just want to chat? feel free to contact me on discord at
+Kyren223 or email me at `contact at kyren.codes`.
diff --git a/blogs/the-search-for-the-perfect-ssh-key.md b/blogs/the-search-for-the-perfect-ssh-key.md
new file mode 100644
index 0000000..09cc13c
--- /dev/null
+++ b/blogs/the-search-for-the-perfect-ssh-key.md
@@ -0,0 +1,117 @@
+---
+title: The search for the perfect SSH key
+description: How you can embed your name in an SSH key
+date: 2024-11-15
+---
+
+SSH keys are a widely used tool among developers,
+they are the equivalent of a username and password in the development world.
+They allow you to prove your identity and they are the gateway
+into accessing servers remotely in a secure manner.
+
+## The SSH public key format
+
+When generating an ssh key, you get 2 files, one with no extension that stores the private key
+and another that ends with a `.pub`.
+The public key is what you share publicly on your profile or add to a remote server to login through SSH.
+
+Here's how a typical SSH public key looks
+
+```
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBSSj+yfJLWEb+Df4r4603TOFAUBREYS43qQB+c9i9UW
+```
+
+It consists of 2 parts, the algorithm, in this case it's
+`ssh-ed25519`, but it can be other ones too such as `ssh-rsa`.
+The second part is a base64 encoded custom binary format.
+
+Let's break it down
+
+- the length of the algorithm string, for ed25519 it's always 11 (4 bytes long)
+- the algorithm type, for ed25519 it's always `ssh-ed25519` in binary (11 bytes long)
+- the length of the key, 32 bytes for ed25519 (4 bytes long)
+- the raw bytes for the ed25519 key (32 bytes long)
+
+## Embedding a word in base64
+
+Base64 is a widely used format for displaying binary in a compact and human readable way,
+it uses A-Z, a-z, 0-9, '/' and '+' adding up to 64 unique characters.
+
+It's possible to choose specific bytes in a way that when encoding it with base64
+it will form a word.
+
+For example, to encode "Kyren" in base64, we can use the bytes
+`00101011 00101010 01001110` or in hex `2b 2a de`.
+
+So by controlling the 32 bytes at the end of the ssh we are able to get
+a ssh public key containig any keyword we want.
+
+## Brute Force
+
+Unfortunately this approach won't work, we still want to be able to use the key
+so we will need to know the corresponding private key, but it's impossible to reverse engineer
+the private key using the public one, that's why cryptography is so secure.
+
+The solution is simple, just generate a bunch of keys until we get lucky and find the desired keyword.
+So that's what I have done, here's a basic go program that does exactly that.
+
+```go
+package main
+
+import (
+ "bytes"
+ "crypto/ed25519"
+ "encoding/base64"
+ "fmt"
+
+ "golang.org/x/crypto/ssh"
+)
+
+func main() {
+ keyword := []byte("Kyren")
+ for {
+ pubKey, privKey, _ := ed25519.GenerateKey(nil)
+ sshPubKey, _ := ssh.NewPublicKey(pubKey)
+ pub := ssh.MarshalAuthorizedKey(sshPubKey)
+ if bytes.Contains(pub, keyword) {
+ privBase64 := base64.StdEncoding.EncodeToString(privKey)
+ fmt.Printf("Found %s in %s with private key %s\n", string(keyword), string(pub), privBase64)
+ break
+ }
+ }
+}
+```
+
+I have later improved this program by adding multithreading and made it easier to use.
+The final code is available on my GitHub [at this link](https://github.com/Kyren223/ed25519-key-gen).
+
+## Results
+
+After running it for 2 days (about 24 hours in total) on my AMD Ryzen 5 7530U laptop (12 threads),
+here are the results:
+
+```
+Completed Search
+Time Elapsed: 12h9m11.225248108s
+Searched: 6733606724 Found: 737
+```
+
+- Generated and searched 150k SSH keys per second
+- Found a 5-character keyword every 9.1 million keys or about 1 every minute
+- Found a 6 character keyword every 1/64 of every 5-character keyword, or about 1 every hour
+- Found 5-character that are at the end of the SSH key 1/50 of every 5-character keyword
+
+My goal was to find a `/kyren` at the end of the key,
+on average that'd be 1 every 3200 5-character keywords.
+
+At the end after only 1862 keywords I found the key, here it is
+
+```
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7P9K9D5RkBk+JCRRS6AtHuTAc6cRpXfRfRMg/Kyren
+```
+
+I hope this inspires you to try and find your own SSH key,
+hopefully your name is not 7 characters long,
+and if it's 8, I hope you have 170 years to spare.
+
+Thanks for reading! this is my first blog post so any feedback will be appreciated!
diff --git a/go.mod b/go.mod
index 6a55876..920e848 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,7 @@ module website
go 1.26.8
-require github.com/a-h/templ v0.3.1070 // indirect
+require (
+ github.com/a-h/templ v0.3.1070 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
index 285a15f..2113e30 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,5 @@
github.com/a-h/templ v0.3.1070 h1:3iCOXHXqFXa99OQzgkUOLJbNzv/QjFGLwKZkBAg+ADA=
github.com/a-h/templ v0.3.1070/go.mod h1:iIqB3g2RHBe/WlnPwa+Bvhh1TKXHWLRnBD9NWHOLm78=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/public/copy.js b/public/copy.js
new file mode 100644
index 0000000..751f2ae
--- /dev/null
+++ b/public/copy.js
@@ -0,0 +1,14 @@
+document.addEventListener("click", async (e) => {
+ const el = e.target.closest("[data-copy]");
+ if (!el) return;
+ try {
+ await navigator.clipboard.writeText(el.dataset.copy);
+ } catch (err) {
+ console.error("copy failed:", err);
+ return;
+ }
+ const scope = el.closest("[data-copy-scope]") ?? el;
+ scope.setAttribute("data-copied", "");
+ clearTimeout(scope._copyTimer);
+ scope._copyTimer = setTimeout(() => scope.removeAttribute("data-copied"), 1500);
+});
diff --git a/public/main.css b/public/main.css
index f93965c..772f896 100644
--- a/public/main.css
+++ b/public/main.css
@@ -5,3 +5,8 @@
@source "../**/*.go";
@source "../**/*.templ";
+
+@keyframes pop { 50% { transform: scale(1.3); } }
+[data-copied] svg { animation: pop 0.25s; }
+
+@keyframes bump { 50% { transform: scale(1.03); } }
diff --git a/public/my_uptime.js b/public/my_uptime.js
new file mode 100644
index 0000000..25f549a
--- /dev/null
+++ b/public/my_uptime.js
@@ -0,0 +1,18 @@
+async function updateUptime() {
+ try {
+ const res = await fetch("/api/my-uptime");
+ if (!res.ok) return;
+ const data = await res.json();
+
+ const text = document.getElementById("Uptime");
+
+ if (text && text.textContent !== data.uptime) {
+ text.textContent = data.uptime;
+ }
+ } catch (e) {}
+}
+
+// Run on page load after all DOM elements loaded
+DOMContentLoaded(updateUptime());
+
+setInterval(updateUptime, 5 * 1000); // in ms
diff --git a/public/scrollbar.js b/public/scrollbar.js
new file mode 100644
index 0000000..da6aded
--- /dev/null
+++ b/public/scrollbar.js
@@ -0,0 +1,52 @@
+function initScrollbox(box) {
+ const area = box.querySelector("[data-scroll-area]");
+ const track = box.querySelector("[data-scroll-track]");
+ const thumb = box.querySelector("[data-scroll-thumb]");
+
+ const inset = () => parseFloat(getComputedStyle(thumb).left) || 0;
+
+ const update = () => {
+ const pad = inset();
+ const room = track.clientHeight - pad * 2;
+ const maxScroll = area.scrollHeight - area.clientHeight;
+ const ratio = Math.min(1, area.clientHeight / area.scrollHeight);
+ const height = Math.max(room * ratio, thumb.clientWidth);
+ const progress = maxScroll > 0 ? area.scrollTop / maxScroll : 0;
+ thumb.style.height = `${height}px`;
+ thumb.style.top = `${pad + progress * (room - height)}px`;
+ };
+
+ thumb.addEventListener("pointerdown", (e) => {
+ e.preventDefault();
+ thumb.setPointerCapture(e.pointerId);
+ const startY = e.clientY;
+ const startTop = parseFloat(thumb.style.top) || 0;
+
+ const move = (ev) => {
+ const pad = inset();
+ const room = track.clientHeight - pad * 2 - thumb.offsetHeight;
+ const top = Math.min(
+ Math.max(startTop - pad + ev.clientY - startY, 0),
+ room,
+ );
+ const progress = room > 0 ? top / room : 0;
+ area.scrollTop = progress * (area.scrollHeight - area.clientHeight);
+ };
+ const stop = () => {
+ thumb.removeEventListener("pointermove", move);
+ thumb.removeEventListener("pointerup", stop);
+ thumb.removeEventListener("pointercancel", stop);
+ };
+ thumb.addEventListener("pointermove", move);
+ thumb.addEventListener("pointerup", stop);
+ thumb.addEventListener("pointercancel", stop);
+ });
+
+ area.addEventListener("scroll", update, { passive: true });
+ const observer = new ResizeObserver(update);
+ observer.observe(area);
+ for (const child of area.children) observer.observe(child);
+ update();
+}
+
+document.querySelectorAll("[data-scrollbox]").forEach(initScrollbox);
diff --git a/public/style.css b/public/style.css
index d1e775f..c555b7d 100644
--- a/public/style.css
+++ b/public/style.css
@@ -13,6 +13,8 @@
--text-3xl--line-height: calc(2.25 / 1.875);
--font-weight-normal: 400;
--font-weight-bold: 700;
+ --default-transition-duration: 150ms;
+ --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
@@ -178,18 +180,54 @@
.fixed {
position: fixed;
}
+ .relative {
+ position: relative;
+ }
.static {
position: static;
}
+ .inset-x-\[0\.3rem\] {
+ inset-inline: 0.3rem;
+ }
+ .top-1 {
+ top: var(--spacing);
+ }
+ .top-1\/2 {
+ top: calc(1 / 2 * 100%);
+ }
.top-\[-0\.7em\] {
top: -0.7em;
}
+ .top-\[-0\.75rem\] {
+ top: -0.75rem;
+ }
+ .top-\[0\.6rem\] {
+ top: 0.6rem;
+ }
+ .right-\[0\.6rem\] {
+ right: 0.6rem;
+ }
+ .right-full {
+ right: 100%;
+ }
.bottom-\[-0\.7em\] {
bottom: -0.7em;
}
+ .bottom-\[-0\.75rem\] {
+ bottom: -0.75rem;
+ }
.left-\[0\.5em\] {
left: 0.5em;
}
+ .left-\[0\.625rem\] {
+ left: 0.625rem;
+ }
+ .col-span-2 {
+ grid-column: span 2 / span 2;
+ }
+ .-m-\[0\.4rem\] {
+ margin: calc(0.4rem * -1);
+ }
.m-0 {
margin: 0px;
}
@@ -217,6 +255,9 @@
.my-\[5vw\] {
margin-block: 5vw;
}
+ .mt-\[0\.5rem\] {
+ margin-top: 0.5rem;
+ }
.mt-\[0\.625vw\] {
margin-top: 0.625vw;
}
@@ -229,6 +270,9 @@
.mt-\[10vw\] {
margin-top: 10vw;
}
+ .mr-2 {
+ margin-right: calc(var(--spacing) * 2);
+ }
.mr-\[0\.1vw\] {
margin-right: 0.1vw;
}
@@ -241,21 +285,24 @@
.mb-6 {
margin-bottom: calc(var(--spacing) * 6);
}
- .mb-6\.25 {
- margin-bottom: calc(var(--spacing) * 6.25);
- }
- .mb-\[1\.5625rem\] {
- margin-bottom: 1.5625rem;
+ .mb-\[0\.30rem\] {
+ margin-bottom: 0.30rem;
}
.mb-\[1\.5625vw\] {
margin-bottom: 1.5625vw;
}
+ .mb-\[1rem\] {
+ margin-bottom: 1rem;
+ }
.mb-\[5vw\] {
margin-bottom: 5vw;
}
.mb-\[7vw\] {
margin-bottom: 7vw;
}
+ .-ml-\[0\.1rem\] {
+ margin-left: calc(0.1rem * -1);
+ }
.ml-\[-4vw\] {
margin-left: -4vw;
}
@@ -280,6 +327,9 @@
.h-0 {
height: 0px;
}
+ .h-38 {
+ height: calc(var(--spacing) * 38);
+ }
.h-\[9\.5vw\] {
height: 9.5vw;
}
@@ -307,42 +357,24 @@
.max-h-\[80svh\] {
max-height: 80svh;
}
+ .min-h-0 {
+ min-height: 0px;
+ }
.w-0 {
width: 0px;
}
- .w-300 {
- width: calc(var(--spacing) * 300);
+ .w-8 {
+ width: calc(var(--spacing) * 8);
}
.w-\[30\%\] {
width: 30%;
}
- .w-\[40rem\] {
- width: 40rem;
- }
.w-\[67\%\] {
width: 67%;
}
.w-\[70\%\] {
width: 70%;
}
- .w-\[75\.0rem\] {
- width: 75.0rem;
- }
- .w-\[75\.001rem\] {
- width: 75.001rem;
- }
- .w-\[75\.1rem\] {
- width: 75.1rem;
- }
- .w-\[75\.12rem\] {
- width: 75.12rem;
- }
- .w-\[75\.13rem\] {
- width: 75.13rem;
- }
- .w-\[75\.123rem\] {
- width: 75.123rem;
- }
.w-\[75rem\] {
width: 75rem;
}
@@ -364,18 +396,41 @@
.flex-1 {
flex: 1;
}
+ .flex-shrink {
+ flex-shrink: 1;
+ }
+ .shrink-0 {
+ flex-shrink: 0;
+ }
.border-collapse {
border-collapse: collapse;
}
+ .-translate-y-1 {
+ --tw-translate-y: calc(var(--spacing) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+ .-translate-y-1\/2 {
+ --tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
+ .cursor-grab {
+ cursor: grab;
+ }
.cursor-pointer {
cursor: pointer;
}
+ .touch-none {
+ touch-action: none;
+ }
.resize {
resize: both;
}
+ .\[scrollbar-width\:none\] {
+ scrollbar-width: none;
+ }
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -385,6 +440,9 @@
.grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
+ .grid-cols-\[29\%_1fr\] {
+ grid-template-columns: 29% 1fr;
+ }
.flex-col {
flex-direction: column;
}
@@ -394,21 +452,24 @@
.justify-between {
justify-content: space-between;
}
+ .gap-4 {
+ gap: calc(var(--spacing) * 4);
+ }
.gap-10 {
gap: calc(var(--spacing) * 10);
}
.gap-16 {
gap: calc(var(--spacing) * 16);
}
- .gap-20 {
- gap: calc(var(--spacing) * 20);
- }
.gap-\[1\.25vw\] {
gap: 1.25vw;
}
.gap-\[1ch\] {
gap: 1ch;
}
+ .gap-x-8 {
+ column-gap: calc(var(--spacing) * 8);
+ }
.gap-x-\[5vw\] {
column-gap: 5vw;
}
@@ -417,6 +478,9 @@
margin-inline-start: calc(1.25vw * var(--tw-space-x-reverse));
margin-inline-end: calc(1.25vw * calc(1 - var(--tw-space-x-reverse)));
}
+ .gap-y-4 {
+ row-gap: calc(var(--spacing) * 4);
+ }
.truncate {
overflow: hidden;
text-overflow: ellipsis;
@@ -434,29 +498,16 @@
.overflow-y-auto {
overflow-y: auto;
}
- .border {
- border-style: var(--tw-border-style);
- border-width: 1px;
- }
- .border-0 {
- border-style: var(--tw-border-style);
- border-width: 0px;
+ .rounded-\[0\.4rem\] {
+ border-radius: 0.4rem;
}
- .border-1 {
+ .border {
border-style: var(--tw-border-style);
border-width: 1px;
}
- .border-2 {
- border-style: var(--tw-border-style);
- border-width: 2px;
- }
- .border-3 {
- border-style: var(--tw-border-style);
- border-width: 3px;
- }
- .border-10 {
+ .border-\[0\.1rem\] {
border-style: var(--tw-border-style);
- border-width: 10px;
+ border-width: 0.1rem;
}
.border-\[0\.1vw\] {
border-style: var(--tw-border-style);
@@ -466,10 +517,6 @@
border-style: var(--tw-border-style);
border-width: 0.2rem;
}
- .border-\[0\.25rem\] {
- border-style: var(--tw-border-style);
- border-width: 0.25rem;
- }
.border-\[0\.25vh\] {
border-style: var(--tw-border-style);
border-width: 0.25vh;
@@ -494,6 +541,15 @@
.border-\[var\(--alt\)\] {
border-color: var(--alt);
}
+ .bg-\(--background\) {
+ background-color: var(--background);
+ }
+ .bg-\(--secondary\) {
+ background-color: var(--secondary);
+ }
+ .bg-\[var\(--background\)\] {
+ background-color: var(--background);
+ }
.fill-\[\#7ed4fb\]\/50 {
fill: color-mix(in oklab, #7ed4fb 50%, transparent);
}
@@ -509,57 +565,57 @@
.p-0 {
padding: 0px;
}
- .p-2 {
- padding: calc(var(--spacing) * 2);
- }
- .p-3 {
- padding: calc(var(--spacing) * 3);
- }
.p-4 {
padding: calc(var(--spacing) * 4);
}
.p-8 {
padding: calc(var(--spacing) * 8);
}
- .p-10 {
- padding: calc(var(--spacing) * 10);
+ .p-\[0\.4rem\] {
+ padding: 0.4rem;
}
.p-\[0\.8em\] {
padding: 0.8em;
}
- .p-\[2\.0rem\] {
- padding: 2.0rem;
- }
- .p-\[2\.2rem\] {
- padding: 2.2rem;
+ .p-\[1rem\] {
+ padding: 1rem;
}
.p-\[2\.2svh\] {
padding: 2.2svh;
}
- .p-\[2\.5rem\] {
- padding: 2.5rem;
- }
- .p-\[2\.rem\] {
- padding: 2.rem;
- }
- .p-\[2rem\] {
- padding: 2rem;
- }
.p-\[3vw\] {
padding: 3vw;
}
+ .px-3 {
+ padding-inline: calc(var(--spacing) * 3);
+ }
+ .px-3\.5 {
+ padding-inline: calc(var(--spacing) * 3.5);
+ }
.px-\[0\.1vw\] {
padding-inline: 0.1vw;
}
+ .px-\[0\.4rem\] {
+ padding-inline: 0.4rem;
+ }
.px-\[0\.5em\] {
padding-inline: 0.5em;
}
+ .px-\[0\.625rem\] {
+ padding-inline: 0.625rem;
+ }
+ .py-\(--ypad\) {
+ padding-block: var(--ypad);
+ }
.py-16 {
padding-block: calc(var(--spacing) * 16);
}
.py-\[0\.625vw\] {
padding-block: 0.625vw;
}
+ .pt-3 {
+ padding-top: calc(var(--spacing) * 3);
+ }
.pt-\[1\.0625em\] {
padding-top: 1.0625em;
}
@@ -569,6 +625,9 @@
.pt-\[5vw\] {
padding-top: 5vw;
}
+ .pt-\[calc\(var\(--ypad\)\+0\.0625rem\)\] {
+ padding-top: calc(var(--ypad) + 0.0625rem);
+ }
.pr-\[1\.25vw\] {
padding-right: 1.25vw;
}
@@ -584,19 +643,24 @@
.font-mono {
font-family: var(--font-mono);
}
- .text-3xl {
- font-size: var(--text-3xl);
- line-height: var(--tw-leading, var(--text-3xl--line-height));
+ .text-\[0\.8rem\] {
+ font-size: 0.8rem;
}
.text-\[0\.96vw\] {
font-size: 0.96vw;
}
+ .text-\[0\.675rem\] {
+ font-size: 0.675rem;
+ }
.text-\[1\.125rem\] {
font-size: 1.125rem;
}
.text-\[1\.375rem\] {
font-size: 1.375rem;
}
+ .text-\[1rem\] {
+ font-size: 1rem;
+ }
.text-\[2\.5vw\] {
font-size: 2.5vw;
}
@@ -618,6 +682,14 @@
.text-\[130\%\] {
font-size: 130%;
}
+ .leading-\[normal\] {
+ --tw-leading: normal;
+ line-height: normal;
+ }
+ .font-\[150\%\] {
+ --tw-font-weight: 150%;
+ font-weight: 150%;
+ }
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
@@ -626,18 +698,60 @@
--tw-font-weight: var(--font-weight-normal);
font-weight: var(--font-weight-normal);
}
+ .wrap-anywhere {
+ overflow-wrap: anywhere;
+ }
+ .whitespace-nowrap {
+ white-space: nowrap;
+ }
+ .text-\(--accent\) {
+ color: var(--accent);
+ }
+ .text-\(--alt\) {
+ color: var(--alt);
+ }
+ .text-\(--extra\) {
+ color: var(--extra);
+ }
.text-\(--primary\) {
color: var(--primary);
}
+ .text-\(--secondary\) {
+ color: var(--secondary);
+ }
+ .text-\[\#54D7A9\] {
+ color: #54D7A9;
+ }
+ .text-\[\#D8C44B\] {
+ color: #D8C44B;
+ }
+ .text-\[\#F16265\] {
+ color: #F16265;
+ }
+ .text-\[--\] {
+ color: --;
+ }
+ .text-\[--accent\] {
+ color: --accent;
+ }
+ .text-\[--extra\] {
+ color: --extra;
+ }
+ .text-\[--secondary\] {
+ color: --secondary;
+ }
+ .text-\[color\:var\(--alt\)\] {
+ color: var(--alt);
+ }
.text-\[var\(--accent\)\] {
color: var(--accent);
}
- .text-\[var\(--primary\)\] {
- color: var(--primary);
- }
.text-\[var\(--secondary\)\] {
color: var(--secondary);
}
+ .no-underline {
+ text-decoration-line: none;
+ }
.underline {
text-decoration-line: underline;
}
@@ -648,6 +762,27 @@
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
+ .transition-colors {
+ transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .select-text {
+ -webkit-user-select: text;
+ user-select: text;
+ }
+ .\[--border-py\:0\.5rem\] {
+ --border-py: 0.5rem;
+ }
+ .\[--border-py\:0\.75rem\] {
+ --border-py: 0.75rem;
+ }
+ .\[--border-py\:0\.875rem\] {
+ --border-py: 0.875rem;
+ }
+ .\[--ypad\:var\(--border-py\,0\.875rem\)\] {
+ --ypad: var(--border-py,0.875rem);
+ }
@media (hover: hover) {
.group-hover\:text-\[var\(--alt\)\]:is(:where(.group):hover *) {
color: var(--alt);
@@ -658,10 +793,29 @@
.group-hover\:underline:is(:where(.group):hover *) {
text-decoration-line: underline;
}
- .hover\:text-\(--accent\):hover {
- color: var(--accent);
+ }
+ .group-data-copied\:block:is(:where(.group)[data-copied] *) {
+ display: block;
+ }
+ .group-data-copied\/copy\:block:is(:where(.group\/copy)[data-copied] *) {
+ display: block;
+ }
+ .group-data-copied\/copy\:bg-\(--primary\)\/25\!:is(:where(.group\/copy)[data-copied] *) {
+ background-color: var(--primary) !important;
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--primary) 25%, transparent) !important;
}
- .hover\:text-\[var\(--accent\)\]:hover {
+ }
+ @media (hover: hover) {
+ .hover\:bg-\(--alt\)\/25:hover {
+ background-color: var(--alt);
+ }
+ @supports (color: color-mix(in lab, red, red)) {
+ .hover\:bg-\(--alt\)\/25:hover {
+ background-color: color-mix(in oklab, var(--alt) 25%, transparent);
+ }
+ }
+ .hover\:text-\(--accent\):hover {
color: var(--accent);
}
.hover\:text-\[var\(--alt\)\]:hover {
@@ -673,6 +827,18 @@
.hover\:text-gray-700:hover {
color: var(--color-gray-700);
}
+ .hover\:no-underline:hover {
+ text-decoration-line: none;
+ }
+ }
+ .data-copied\:animate-\[bump_0\.25s\][data-copied] {
+ animation: bump 0.25s;
+ }
+ .data-copied\:bg-\(--primary\)\/25\![data-copied] {
+ background-color: var(--primary) !important;
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--primary) 25%, transparent) !important;
+ }
}
@media (width >= 40rem) {
.sm\:invisible {
@@ -835,6 +1001,40 @@
grid-template-columns: repeat(1, minmax(0, 1fr));
}
}
+ .\[\&\:\:-webkit-scrollbar\]\:hidden::-webkit-scrollbar {
+ display: none;
+ }
+ .\[\&\>\*\+\*\]\:mt-\[-0\.1rem\] > * + * {
+ margin-top: -0.1rem;
+ }
+}
+@keyframes pop {
+ 50% {
+ transform: scale(1.3);
+ }
+}
+[data-copied] svg {
+ animation: pop 0.25s;
+}
+@keyframes bump {
+ 50% {
+ transform: scale(1.03);
+ }
+}
+@property --tw-translate-x {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0;
+}
+@property --tw-translate-y {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0;
+}
+@property --tw-translate-z {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0;
}
@property --tw-rotate-x {
syntax: "*";
@@ -866,6 +1066,10 @@
inherits: false;
initial-value: solid;
}
+@property --tw-leading {
+ syntax: "*";
+ inherits: false;
+}
@property --tw-font-weight {
syntax: "*";
inherits: false;
@@ -878,6 +1082,9 @@
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-translate-z: 0;
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
@@ -885,6 +1092,7 @@
--tw-skew-y: initial;
--tw-space-x-reverse: 0;
--tw-border-style: solid;
+ --tw-leading: initial;
--tw-font-weight: initial;
--tw-outline-style: solid;
}
diff --git a/public/styles/terminal.css b/public/styles/terminal.css
index f44bcd0..e0e80aa 100644
--- a/public/styles/terminal.css
+++ b/public/styles/terminal.css
@@ -45,3 +45,26 @@ body {
color: var(--accent);
}
}
+
+.scrollbox::-webkit-scrollbar {
+ width: 1.5rem;
+}
+.scrollbox::-webkit-scrollbar-track {
+ background: transparent;
+ border-left: 0.1rem solid var(--alt);
+}
+.scrollbox::-webkit-scrollbar-thumb {
+ background: var(--primary);
+ border: 0.3rem solid transparent;
+ background-clip: padding-box;
+ border-radius: 0;
+}
+.scrollbox::-webkit-scrollbar-thumb:hover {
+ background: var(--accent);
+}
+
+@supports not selector(::-webkit-scrollbar) {
+ .scrollbox {
+ scrollbar-color: var(--primary) transparent;
+ }
+}
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..578be5e
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cleanup() {
+ trap - SIGINT SIGTERM EXIT
+ kill $(jobs -p) 2>/dev/null || true
+}
+trap cleanup SIGINT SIGTERM EXIT
+
+# templ generate --watch --proxy="http://localhost:8080" --cmd="DEV=1 go run ." --watch-pattern='.*' &
+templ generate --watch --proxy="http://localhost:8080" --cmd="DEV=1 go run ." --watch-pattern='(.+\.go$)|(.+\.templ$)|(.+_templ\.txt$)|(.+\.css$)|(.+\.js$)|(.+\.md$)|(.+\.svg$)|(.+\.json$)|(.+\.html$)|(.+\.txt$)|(.+\.ttf$)|(.+\.woff2?$)|(.+\.png$)|(.+\.jpg$)|(.+\.webp$)' &
+TEMPL_PID=$!
+
+tail -f /dev/null | tailwindcss -i ./public/main.css -o ./public/style.css --watch &
+TAILWIND_PID=$!
+
+echo "[dev] Watching Templ (PID $TEMPL_PID) and Tailwind (PID $TAILWIND_PID)..."
+
+wait -n
+
+echo "[dev] A background process exited unexpectedly."
diff --git a/website.go b/website.go
index 8802bd1..b8c0e76 100644
--- a/website.go
+++ b/website.go
@@ -1,15 +1,28 @@
package main
import (
+ "cmp"
+ "embed"
"encoding/json"
+ "errors"
"fmt"
+ "io/fs"
+ "log"
"net/http"
+ "os"
+ "regexp"
+ "slices"
"strings"
"sync"
+ "time"
"github.com/a-h/templ"
+ "gopkg.in/yaml.v3"
)
+//go:embed public blogs
+var embedded embed.FS
+
func Assert(ok bool) {
if !ok {
panic("assertion failure")
@@ -128,6 +141,117 @@ func styleToCss(style Style) templ.SafeCSS {
return templ.SafeCSS(b.String())
}
+type Blog struct {
+ Slug string
+ Title string
+ Desc string
+ Date time.Time
+ Body string // raw markdown for rendering
+}
+
+type frontmatter struct {
+ Title string `yaml:"title"`
+ Desc string `yaml:"description"`
+ Date time.Time `yaml:"date"`
+}
+
+var datePrefix = regexp.MustCompile(`^(\d{4}-\d{2}-\d{2})-(.+)\.md$`)
+
+func parseBlog(name, raw string) (Blog, error) {
+ raw = strings.ReplaceAll(raw, "\r\n", "\n")
+ body := raw
+ var frontmatter frontmatter
+ if rest, ok := strings.CutPrefix(raw, "---\n"); ok {
+ if head, after, found := strings.Cut(rest, "\n---\n"); found {
+ if err := yaml.Unmarshal([]byte(head), &frontmatter); err != nil {
+ return Blog{}, err
+ }
+ body = after
+ }
+ }
+
+ slug := strings.TrimSuffix(name, ".md")
+ date := frontmatter.Date // frontmatter wins
+ if match := datePrefix.FindStringSubmatch(name); match != nil {
+ slug = match[2]
+ if date.IsZero() {
+ date, _ = time.Parse(time.DateOnly, match[1])
+ }
+ }
+ if date.IsZero() {
+ return Blog{}, errors.New("no date in frontmatter or filename")
+ }
+
+ title := frontmatter.Title
+ if title == "" {
+ title = slug
+ }
+ return Blog{
+ Slug: slug,
+ Title: title,
+ Date: date,
+ Body: body,
+ Desc: frontmatter.Desc,
+ }, nil
+}
+
+func LoadBlogs(fsys fs.FS) ([]Blog, error) {
+ entries, err := fs.ReadDir(fsys, ".")
+ if err != nil {
+ return nil, err
+ }
+ var blogs []Blog
+ for _, e := range entries {
+ if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") || strings.HasPrefix(e.Name(), "_") {
+ continue
+ }
+ raw, err := fs.ReadFile(fsys, e.Name())
+ if err != nil {
+ return nil, err
+ }
+ blog, err := parseBlog(e.Name(), string(raw))
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", e.Name(), err)
+ }
+ blogs = append(blogs, blog)
+ }
+ slices.SortFunc(blogs, func(a, b Blog) int { // newest first
+ return cmp.Or(b.Date.Compare(a.Date), strings.Compare(a.Slug, b.Slug))
+ })
+ return blogs, nil
+}
+
+func loadBlogs() ([]Blog, error) {
+ blogFS, err := fs.Sub(embedded, "blogs")
+ if err != nil {
+ return nil, err
+ }
+ return LoadBlogs(blogFS)
+}
+
+var (
+ devMode = os.Getenv("DEV") != ""
+ cachedBlogs []Blog
+)
+
+func getBlogs() []Blog {
+ if devMode || cachedBlogs == nil {
+ var err error
+ cachedBlogs, err = loadBlogs()
+ if err != nil {
+ log.Println("reloading blogs failed:", err)
+ }
+ }
+ return cachedBlogs
+}
+
+func latestBlog(blogs []Blog) *Blog {
+ if len(blogs) == 0 {
+ return nil
+ }
+ return &blogs[0]
+}
+
type Project struct {
Name string
Type string
@@ -193,10 +317,76 @@ func styleHandler(cookie string, lookup func(string) (map[string]string, bool))
}
}
+func notFoundHandler(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ NotFoundPage(currentStyle(r), r.URL.Path).Render(r.Context(), w)
+}
+
+type MyUptime struct {
+ mu sync.RWMutex
+ Uptime string
+ LastUpdate time.Time
+}
+
+var myUptime MyUptime
+
+// GetMyUptime returns the current status string (handles offline state)
+func GetMyUptime() string {
+ myUptime.mu.RLock()
+ defer myUptime.mu.RUnlock()
+
+ // If no ping received in 3 minutes, treat as offline
+ if time.Since(myUptime.LastUpdate) > 1*time.Minute {
+ return "Asleep"
+ }
+
+ uptime := myUptime.Uptime
+ uptime, _ = strings.CutPrefix(uptime, "up ")
+ uptime = "Awake for " + uptime
+
+ return uptime
+}
+
+func handleUptimePing(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodGet {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{
+ "uptime": GetMyUptime(),
+ })
+ } else if r.Method == http.MethodPost {
+ if devMode == false {
+ secretToken := os.Getenv("MY_UPTIME_TOKEN")
+ if r.Header.Get("Authorization") != "Bearer "+secretToken {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+ }
+
+ var req struct {
+ Uptime string `json:"uptime"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Bad request", http.StatusBadRequest)
+ return
+ }
+
+ myUptime.mu.Lock()
+ myUptime.Uptime = req.Uptime
+ myUptime.LastUpdate = time.Now()
+ myUptime.mu.Unlock()
+
+ w.WriteHeader(http.StatusOK)
+ } else {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+}
+
func main() {
carbonightCat := Theme{
primary: "#94e2d5",
- secondary: "#b4befe",
+ // secondary: "#b4befe",
+ secondary: "#BB91F0",
accent: "#74c7ec",
extra: "#a6e3a1",
text: "#cdd6f4",
@@ -269,11 +459,14 @@ func main() {
mux := http.NewServeMux()
- fileServer := http.FileServer(http.Dir("./public"))
- mux.Handle("GET /public/", http.StripPrefix("/public/", fileServer))
+ mux.Handle("GET /public/", http.FileServerFS(embedded))
+ mux.HandleFunc("GET /api/my-uptime", handleUptimePing)
+ mux.HandleFunc("POST /api/my-uptime", handleUptimePing)
+
+ mux.HandleFunc("/", notFoundHandler) // matches everything
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
- HomePage(currentStyle(r)).Render(r.Context(), w)
+ HomePage(currentStyle(r), latestBlog(getBlogs())).Render(r.Context(), w)
})
mux.HandleFunc("GET /skills", func(w http.ResponseWriter, r *http.Request) {
@@ -284,6 +477,10 @@ func main() {
ProjectsPage(currentStyle(r), projects).Render(r.Context(), w)
})
+ mux.HandleFunc("GET /blogs", func(w http.ResponseWriter, r *http.Request) {
+ BlogsPage(currentStyle(r), getBlogs()).Render(r.Context(), w)
+ })
+
mux.HandleFunc("GET /theme/{name}", styleHandler("theme", func(n string) (map[string]string, bool) {
theme, ok := GetTheme(n)
return theme.Vars(), ok
diff --git a/website.templ b/website.templ
index face9f9..1f4f636 100644
--- a/website.templ
+++ b/website.templ
@@ -19,18 +19,97 @@ templ HeadComponent(title string, description string) {
<meta property="og:image" content="https://kyren.codes/image.png"/>
<meta property="og:image:alt" content="a cyan sphere with a dark background"/>
<meta property="og:site_name" content="Kyren's Website"/>
+ <script src="/public/copy.js" defer></script>
<script src="/public/theme.js" defer></script>
+ <script src="/public/scrollbar.js" defer></script>
+ <script src="/public/my_uptime.js" defer></script>
<link rel="stylesheet" href="/public/style.css"/>
{ children... }
</head>
}
+templ BlogItem(header string, blog Blog, extra string) {
+ @TerminalBorder(header, blog.Date.Format("January 2, 2006"), extra) {
+ <span class="cursor-pointer group block">
+ <a href={ templ.SafeURL("/blogs/" + blog.Slug) } class="no-underline hover:no-underline">
+ <h1 class="text-[130%] font-bold text-(--accent) group-hover:underline">
+ { blog.Title }
+ </h1>
+ <p class="text-(--secondary)">{ blog.Desc }</p>
+ </a>
+ </span>
+ }
+}
+
+templ Link(href string, newTab bool) {
+ <a
+ href={ templ.SafeURL(href) }
+ if newTab {
+ target="_blank"
+ rel="noopener noreferrer"
+ }
+ >
+ { children... }
+ </a>
+}
+
+templ TerminalStack(joined bool, extra string) {
+ <div class={ "flex flex-col min-h-0", templ.KV("[&>*+*]:mt-[-0.1rem]", joined), templ.KV("gap-4", !joined), extra }>
+ { children... }
+ </div>
+}
+
+templ TerminalBorder(header string, footer string, extra string) {
+ <div class={ "relative flex px-3.5 py-(--ypad) pt-[calc(var(--ypad)+0.0625rem)] [--ypad:var(--border-py,0.875rem)] border-[0.1rem] border-solid border-[color:var(--alt)]", extra }>
+ <span class="absolute px-[0.625rem] text-[1rem] left-[0.625rem] top-[-0.75rem] bg-[var(--background)] text-[color:var(--alt)]">{ header }</span>
+ <span class="absolute px-[0.625rem] text-[1rem] left-[0.625rem] bottom-[-0.75rem] bg-[var(--background)] text-[color:var(--alt)]">{ footer }</span>
+ { children... }
+ </div>
+}
+
+templ CopyIcon() {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy preview-icon"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>
+}
+
+templ TerminalBorderWithCopy(header string, footer string, extra string, text string, href string, showLabel bool) {
+ @TerminalBorder(header, footer, extra) {
+ <div data-copy-scope class="group/copy flex w-full items-center justify-between gap-4">
+ if href != "" {
+ <a href={ templ.SafeURL(href) } class="wrap-anywhere">
+ { children... }
+ </a>
+ } else {
+ <span data-copy={ text } class="cursor-pointer wrap-anywhere">
+ { children... }
+ </span>
+ }
+ <span class="relative shrink-0">
+ if showLabel {
+ <span class="hidden group-data-copied/copy:block absolute right-full top-1/2 -translate-y-1/2 mr-2 px-[0.4rem] whitespace-nowrap bg-(--background) text-(--primary)">copied</span>
+ }
+ <button type="button" data-copy={ text } aria-label="copy" class="block p-[0.4rem] rounded-[0.4rem] cursor-pointer transition-colors hover:bg-(--alt)/25 group-data-copied/copy:bg-(--primary)/25!">
+ @CopyIcon()
+ </button>
+ </span>
+ </div>
+ }
+}
+
+templ CopyPre(text string, preClass string) {
+ <button type="button" data-copy={ text } class="group relative shrink-0 cursor-pointer text-left rounded-[0.4rem] p-[0.4rem] -m-[0.4rem] transition-colors hover:bg-(--alt)/25 data-copied:bg-(--primary)/25! data-copied:animate-[bump_0.25s]">
+ <pre class={ preClass }>
+ { children... }
+ </pre>
+ <span class="hidden group-data-copied:block absolute right-[0.6rem] top-[0.6rem] px-[0.4rem] bg-(--background) text-(--primary) text-[0.8rem]">copied</span>
+ </button>
+}
+
templ NavLink(href string, label string) {
<a href={ templ.SafeURL(href) } class="text-(--primary) hover:text-(--accent)">{ label }</a>
}
-templ NavComponent(path string) {
- <nav class="flex justify-between gap-10 p-4 mb-[1.5625rem] border-2 border-(--alt) text-[1.375rem]">
+templ Nav(path string) {
+ <nav class="flex justify-between gap-10 p-4 mb-6 border-[0.125rem] border-(--alt) text-[1.375rem]">
<span>{ path }</span>
<span class="flex gap-[1ch]">
@NavLink("/", "home")
@@ -49,27 +128,109 @@ templ TerminalLayout(style Style, path string) {
<link rel="stylesheet" href="/public/styles/terminal.css"/>
}
<body>
- <main class="w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]">
- @NavComponent(path)
+ <main class="flex flex-col w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]">
+ @Nav(path)
{ children... }
</main>
</body>
</html>
}
-templ HomePage(style Style) {
+const fingerprint = `+--[ED25519 256]--+
+| +. .. +.B|
+| + o..o . BB|
+| ..o oo.o =+E|
+| ... oo. + o.|
+| . S . = |
+| . . + o |
+| o o o . |
+| oo .o+ + |
+| ++ooo+= |
++----[SHA256]-----+`
+const sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7P9K9D5RkBk+JCRRS6AtHuTAc6cRpXfRfRMg/Kyren"
+
+templ HomePage(style Style, latestBlog *Blog) {
@TerminalLayout(style, "/home/kyren/") {
- <div id="content" class="w-0 h-0 invisible sm:w-full sm:h-full sm:visible">
- <h1>Home</h1>
- <p>Go server running with Templ templates and Air hot-reloading.</p>
+ <div class={ "grid grid-cols-[29%_1fr] gap-x-8 flex-1 min-h-0", templ.KV("gap-y-4", !false) }>
+ <div class="flex flex-col min-h-0 [--border-py:0.75rem]">
+ @TerminalStack(true, "") {
+ @TerminalBorder("Profile", "", "[--border-py:0.875rem]") {
+ <div class="flex items-center gap-4 h-38">
+ <img src="/public/favicon.svg" alt="picture" class="h-full object-cover"/>
+ @CopyPre(sshKey, "shrink-0 text-[0.675rem] leading-[normal]") {
+ { fingerprint }
+ }
+ </div>
+ }
+ @TerminalBorderWithCopy("Email", "", "", "contact@kyren.codes", "mailto:contact@kyren.codes", false) {
+ contact@kyren.codes
+ }
+ @TerminalBorder("Git Repos (main)", "", "") {
+ @Link("https://git.kyren.codes", true) {
+ git.kyren.codes
+ }
+ }
+ @TerminalBorder("GitHub (secondary)", "", "") {
+ @Link("https://github.com/kyren223", true) {
+ github.com/Kyren223
+ }
+ }
+ @TerminalBorder("Discord", "", "") {
+ @Link("https://discord.com/users/481884415090229258", true) {
+ Kyren223
+ }
+ }
+ }
+ </div>
+ <div class="flex flex-col gap-10 min-h-0 select-text">
+ @TerminalBorder("Status", "", "") {
+ <div class="flex gap-[1ch] text-[1rem]">
+ <span>Working on</span>
+ <span>
+ @Link("/projects/krypton", false) {
+ Krypton
+ }
+ </span>
+ <span>|</span>
+ <span>Learning Langdev & Compiler Design</span>
+ <span>|</span>
+ <span>Available for hire</span>
+ </div>
+ }
+ @TerminalBorder("About Me", "", "") {
+ Hi! I'm Kyren, a softwarea engineer and game developer.
+ I love working with low level languages and learning things deeply.
+ I am also a terminal nerd and Linux enthusiast who uses NVIM btw.
+ And when I am not coding, I am most likely playing video games,
+ watching Anime, or solving Rubik's cubes fast.
+ }
+ @BlogItem("Latest Blog Post", *latestBlog, "")
+ </div>
+ @TerminalBorderWithCopy("SSH Key", "", footerClass(false), sshKey, "", true) {
+ { sshKey }
+ }
</div>
}
}
+func footerClass(connected bool) string {
+ style := "col-span-2 select-text [--border-py:0.5rem]"
+ if connected {
+ style += " mb-[0.30rem]"
+ }
+ return style
+}
+
templ SkillsPage(style Style) {
@TerminalLayout(style, "/home/kyren/skills.md") {
- <h1 class="text-3xl font-bold">About</h1>
- <p>Single-binary web app using Go's standard library net/http.</p>
+ @TerminalStack(false, "") {
+ @TerminalBorder("Setup", "", "") {
+ <span>
+ $ fastfetch
+ @Fastfetch(fetchRows)
+ </span>
+ }
+ }
}
}
@@ -86,3 +247,174 @@ templ ProjectsPage(style Style, projects []Project) {
</div>
}
}
+
+templ NotFoundPage(style Style, path string) {
+ @TerminalLayout(style, "404") {
+ @TerminalStack(true, "") {
+ @TerminalBorder("Shell", "", "") {
+ <span>
+ <p>$ cd ~{ path }</p>
+ <p class="pt-3">"{ path[1:] }": No such file or directory (os error 2) </p>
+ </span>
+ }
+ @TerminalBorder("", "", "") {
+ <div class="gap-[1ch]">
+ <span>{ ">" }</span>
+ <span class="text-(--secondary)">
+ @Link("/", false) {
+ { "cd $HOME" }
+ }
+ </span>
+ </div>
+ }
+ }
+ }
+}
+
+templ ScrollBox(extra string) {
+ <div data-scrollbox class={ "flex flex-1 min-h-0", extra }>
+ <div data-scroll-area class="flex-1 min-w-0 overflow-y-auto border-[0.1rem] border-solid border-(--alt) p-[1rem] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
+ { children... }
+ </div>
+ <div data-scroll-track class="relative w-8 -ml-[0.1rem] border-[0.1rem] border-solid border-(--alt)">
+ <div data-scroll-thumb class="absolute inset-x-[0.3rem] bg-(--secondary) cursor-grab touch-none"></div>
+ </div>
+ </div>
+}
+
+templ BlogsPage(style Style, blogs []Blog) {
+ @TerminalLayout(style, "blogs") {
+ @TerminalBorder("Shell", "", "mb-[1rem]") {
+ $ ls -lh ~/blogs
+ }
+ @ScrollBox("") {
+ @TerminalStack(false, "mt-[0.5rem]") {
+ for i := 0; i < 1; i++ {
+ for _, blog := range blogs {
+ @BlogItem("", blog, "mb-[1rem]")
+ }
+ }
+ }
+ }
+ }
+}
+
+type FetchRow struct {
+ Icon templ.Component
+ Color string // tailwind text color class, used for the icon and label
+ Label string
+ Value string
+}
+
+templ IconMonitor(color string) {
+ @Icon(color) {
+ <rect width="20" height="14" x="2" y="3" rx="2"></rect>
+ <line x1="8" x2="16" y1="21" y2="21"></line>
+ <line x1="12" x2="12" y1="17" y2="21"></line>
+ }
+}
+
+templ IconTerminal(color string) {
+ @Icon(color) {
+ <polyline points="4 17 10 11 4 5"></polyline>
+ <line x1="12" x2="20" y1="19" y2="19"></line>
+ }
+}
+
+templ IconCode(color string) {
+ @Icon(color) {
+ <polyline points="16 18 22 12 16 6"></polyline>
+ <polyline points="8 6 2 12 8 18"></polyline>
+ }
+}
+
+templ IconCpu(color string) {
+ @Icon(color) {
+ <rect width="16" height="16" x="4" y="4" rx="2"></rect>
+ <rect width="6" height="6" x="9" y="9" rx="1"></rect>
+ <path d="M15 2v2M15 20v2M2 15h2M2 9h2M20 15h2M20 9h2M9 2v2M9 20v2"></path>
+ }
+}
+
+templ IconScriptLang(color string) {
+ @Icon(color) {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-terminal preview-icon"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path><path d="m8 16 2-2-2-2"></path><path d="M12 18h4"></path></svg>
+ }
+}
+
+templ IconEditor(color string) {
+ @Icon(color) {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen preview-icon"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"></path></svg>
+ }
+}
+
+templ IconDebugger(color string) {
+ @Icon(color) {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-off preview-icon"><path d="M12 20v-8"></path><path d="M12.656 7H14a4 4 0 0 1 4 4v1.344"></path><path d="M14.12 3.88 16 2"></path><path d="M17.123 17.123A6 6 0 0 1 6 14v-3a4 4 0 0 1 1.72-3.287"></path><path d="m2 2 20 20"></path><path d="M21 5a4 4 0 0 1-3.55 3.97"></path><path d="M22 13h-3.344"></path><path d="M3 21a4 4 0 0 1 3.81-4"></path><path d="M3 5a4 4 0 0 0 3.55 3.97"></path><path d="M6 13H2"></path><path d="m8 2 1.88 1.88"></path><path d="M9.712 4.06A3 3 0 0 1 15 6v1.13"></path></svg>
+ }
+}
+
+templ IconKeyboard(color string) {
+ @Icon(color) {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-keyboard preview-icon"><path d="M10 8h.01"></path><path d="M12 12h.01"></path><path d="M14 8h.01"></path><path d="M16 12h.01"></path><path d="M18 8h.01"></path><path d="M6 8h.01"></path><path d="M7 16h10"></path><path d="M8 12h.01"></path><rect width="20" height="16" x="2" y="4" rx="2"></rect></svg>
+ }
+}
+
+templ IconUptime(color string) {
+ @Icon(color) {
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock preview-icon"><circle cx="12" cy="12" r="10"></circle><path d="M12 6v6l4 2"></path></svg>
+ }
+}
+
+func row(icon func(color string) templ.Component, color, label, value string) FetchRow {
+ return FetchRow{Icon: icon(color), Color: color, Label: label, Value: value}
+}
+
+var fetchRows = []FetchRow{
+ // row(IconTerminal, "text-(--secondary)", "Terminal", "Ghostty & Zsh"),
+ row(IconCode, "text-(--accent)", "Main Language", "C"),
+ row(IconScriptLang, "text-(--accent)", "Scripting Langs", "Go and Python"),
+ row(IconDebugger, "text-[#F16265]", "Debugger", "Raddebugger"),
+ row(IconMonitor, "text-[#54D7A9]", "Operating System", "Fedora KDE Linux"),
+ row(IconEditor, "text-[#54D7A9]", "Text Editor", "Neovim"),
+ row(IconUptime, "text-[#D8C44B]", "Uptime", GetMyUptime()),
+ // row(IconCpu, "text-(--extra)", "Hardware", "..."),
+}
+
+func branch(i, n int) string {
+ switch {
+ case n == 1:
+ return "─"
+ case i == 0:
+ return "┌"
+ case i == n-1:
+ return "└"
+ default:
+ return "├"
+ }
+}
+
+templ Fastfetch(rows []FetchRow) {
+ <div
+ style="
+ display: grid;
+ grid-template-columns: auto auto auto 1fr;
+ align-items: center;
+ column-gap: 1ch;
+ line-height: normal;
+ "
+ >
+ for i, row := range rows {
+ <span class="text-(--alt)">{ branch(i, len(rows)) }</span>
+ @row.Icon
+ <span class={ row.Color, "font-bold" }>{ row.Label }</span>
+ <span id={ row.Label }>{ row.Value }</span>
+ }
+ </div>
+}
+
+templ Icon(color string) {
+ <svg width="1.1em" height="1.1em" class={ "shrink-0", color } viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ { children... }
+ </svg>
+}
diff --git a/website_templ.go b/website_templ.go
index 5782d94..2798a1a 100644
--- a/website_templ.go
+++ b/website_templ.go
@@ -81,7 +81,7 @@ func HeadComponent(title string, description string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"https://kyren.codes/\"><meta property=\"og:image\" content=\"https://kyren.codes/image.png\"><meta property=\"og:image:alt\" content=\"a cyan sphere with a dark background\"><meta property=\"og:site_name\" content=\"Kyren's Website\"><script src=\"/public/theme.js\" defer></script><link rel=\"stylesheet\" href=\"/public/style.css\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"https://kyren.codes/\"><meta property=\"og:image\" content=\"https://kyren.codes/image.png\"><meta property=\"og:image:alt\" content=\"a cyan sphere with a dark background\"><meta property=\"og:site_name\" content=\"Kyren's Website\"><script src=\"/public/copy.js\" defer></script><script src=\"/public/theme.js\" defer></script><script src=\"/public/scrollbar.js\" defer></script><script src=\"/public/my_uptime.js\" defer></script><link rel=\"stylesheet\" href=\"/public/style.css\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -97,7 +97,7 @@ func HeadComponent(title string, description string) templ.Component {
})
}
-func NavLink(href string, label string) templ.Component {
+func BlogItem(header string, blog Blog, extra string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -118,33 +118,260 @@ func NavLink(href string, label string) templ.Component {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"")
+ templ_7745c5c3_Var7 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"cursor-pointer group block\"><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 templ.SafeURL
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/blogs/" + blog.Slug))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 34, Col: 49}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"no-underline hover:no-underline\"><h1 class=\"text-[130%] font-bold text-(--accent) group-hover:underline\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 string
+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(blog.Title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 36, Col: 17}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h1><p class=\"text-(--secondary)\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 string
+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(blog.Desc)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 38, Col: 45}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p></a></span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder(header, blog.Date.Format("January 2, 2006"), extra).Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func Link(href string, newTab bool) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var11 == nil {
+ templ_7745c5c3_Var11 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var12 templ.SafeURL
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 46, Col: 28}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if newTab {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " target=\"_blank\" rel=\"noopener noreferrer\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func TerminalStack(joined bool, extra string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var13 == nil {
+ templ_7745c5c3_Var13 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var14 = []any{"flex flex-col min-h-0", templ.KV("[&>*+*]:mt-[-0.1rem]", joined), templ.KV("gap-4", !joined), extra}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var13.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func TerminalBorder(header string, footer string, extra string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var16 == nil {
+ templ_7745c5c3_Var16 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var17 = []any{"relative flex px-3.5 py-(--ypad) pt-[calc(var(--ypad)+0.0625rem)] [--ypad:var(--border-py,0.875rem)] border-[0.1rem] border-solid border-[color:var(--alt)]", extra}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var18 string
+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><span class=\"absolute px-[0.625rem] text-[1rem] left-[0.625rem] top-[-0.75rem] bg-[var(--background)] text-[color:var(--alt)]\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var7 templ.SafeURL
- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
+ var templ_7745c5c3_Var19 string
+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(header)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 29, Col: 30}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 64, Col: 137}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"text-(--primary) hover:text-(--accent)\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</span> <span class=\"absolute px-[0.625rem] text-[1rem] left-[0.625rem] bottom-[-0.75rem] bg-[var(--background)] text-[color:var(--alt)]\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var8 string
- templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(label)
+ var templ_7745c5c3_Var20 string
+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(footer)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 29, Col: 87}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 65, Col: 140}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</a>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var16.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -152,7 +379,7 @@ func NavLink(href string, label string) templ.Component {
})
}
-func NavComponent(path string) templ.Component {
+func CopyIcon() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -168,25 +395,316 @@ func NavComponent(path string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var9 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var9 == nil {
- templ_7745c5c3_Var9 = templ.NopComponent
+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var21 == nil {
+ templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<nav class=\"flex justify-between gap-10 p-4 mb-[1.5625rem] border-2 border-(--alt) text-[1.375rem]\"><span>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-copy preview-icon\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"></rect><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var10 string
- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(path)
+ return nil
+ })
+}
+
+func TerminalBorderWithCopy(header string, footer string, extra string, text string, href string, showLabel bool) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var22 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var22 == nil {
+ templ_7745c5c3_Var22 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var23 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div data-copy-scope class=\"group/copy flex w-full items-center justify-between gap-4\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if href != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var24 templ.SafeURL
+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 78, Col: 33}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" class=\"wrap-anywhere\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var22.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</a> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<span data-copy=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var25 string
+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 82, Col: 26}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" class=\"cursor-pointer wrap-anywhere\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var22.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<span class=\"relative shrink-0\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if showLabel {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<span class=\"hidden group-data-copied/copy:block absolute right-full top-1/2 -translate-y-1/2 mr-2 px-[0.4rem] whitespace-nowrap bg-(--background) text-(--primary)\">copied</span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<button type=\"button\" data-copy=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var26 string
+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 90, Col: 42}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" aria-label=\"copy\" class=\"block p-[0.4rem] rounded-[0.4rem] cursor-pointer transition-colors hover:bg-(--alt)/25 group-data-copied/copy:bg-(--primary)/25!\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = CopyIcon().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</button></span></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder(header, footer, extra).Render(templ.WithChildren(ctx, templ_7745c5c3_Var23), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 34, Col: 14}
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func CopyPre(text string, preClass string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var27 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var27 == nil {
+ templ_7745c5c3_Var27 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<button type=\"button\" data-copy=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span> <span class=\"flex gap-[1ch]\">")
+ var templ_7745c5c3_Var28 string
+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 99, Col: 39}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" class=\"group relative shrink-0 cursor-pointer text-left rounded-[0.4rem] p-[0.4rem] -m-[0.4rem] transition-colors hover:bg-(--alt)/25 data-copied:bg-(--primary)/25! data-copied:animate-[bump_0.25s]\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var29 = []any{preClass}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var29...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<pre class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var30 string
+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var29).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var27.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</pre><span class=\"hidden group-data-copied:block absolute right-[0.6rem] top-[0.6rem] px-[0.4rem] bg-(--background) text-(--primary) text-[0.8rem]\">copied</span></button>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func NavLink(href string, label string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var31 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var31 == nil {
+ templ_7745c5c3_Var31 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var32 templ.SafeURL
+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 108, Col: 30}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\" class=\"text-(--primary) hover:text-(--accent)\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var33 string
+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(label)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 108, Col: 87}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func Nav(path string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var34 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var34 == nil {
+ templ_7745c5c3_Var34 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<nav class=\"flex justify-between gap-10 p-4 mb-6 border-[0.125rem] border-(--alt) text-[1.375rem]\"><span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var35 string
+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(path)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 113, Col: 14}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span> <span class=\"flex gap-[1ch]\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -204,7 +722,7 @@ func NavComponent(path string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span></nav>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -228,29 +746,29 @@ func TerminalLayout(style Style, path string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var11 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var11 == nil {
- templ_7745c5c3_Var11 = templ.NopComponent
+ templ_7745c5c3_Var36 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var36 == nil {
+ templ_7745c5c3_Var36 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<!doctype html><html lang=\"en\" style=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<!doctype html><html lang=\"en\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var12 string
- templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(styleToCss(style))
+ var templ_7745c5c3_Var37 string
+ templ_7745c5c3_Var37, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(styleToCss(style))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 47, Col: 42}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 126, Col: 42}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Var13 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@@ -262,29 +780,29 @@ func TerminalLayout(style Style, path string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<link rel=\"stylesheet\" href=\"/public/styles/terminal.css\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<link rel=\"stylesheet\" href=\"/public/styles/terminal.css\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = HeadComponent(path, "My personal website").Render(templ.WithChildren(ctx, templ_7745c5c3_Var13), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = HeadComponent(path, "My personal website").Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<body><main class=\"w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<body><main class=\"flex flex-col w-[75rem] h-[40rem] p-8 border-[0.2rem] border-(--text) overflow-hidden text-[1.125rem]\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = NavComponent(path).Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Nav(path).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = templ_7745c5c3_Var36.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</main></body></html>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</main></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -292,7 +810,20 @@ func TerminalLayout(style Style, path string) templ.Component {
})
}
-func HomePage(style Style) templ.Component {
+const fingerprint = `+--[ED25519 256]--+
+| +. .. +.B|
+| + o..o . BB|
+| ..o oo.o =+E|
+| ... oo. + o.|
+| . S . = |
+| . . + o |
+| o o o . |
+| oo .o+ + |
+| ++ooo+= |
++----[SHA256]-----+`
+const sshKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7P9K9D5RkBk+JCRRS6AtHuTAc6cRpXfRfRMg/Kyren"
+
+func HomePage(style Style, latestBlog *Blog) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -308,12 +839,12 @@ func HomePage(style Style) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var14 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var14 == nil {
- templ_7745c5c3_Var14 = templ.NopComponent
+ templ_7745c5c3_Var39 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var39 == nil {
+ templ_7745c5c3_Var39 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var40 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@@ -325,13 +856,357 @@ func HomePage(style Style) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div id=\"content\" class=\"w-0 h-0 invisible sm:w-full sm:h-full sm:visible\"><h1>Home</h1><p>Go server running with Templ templates and Air hot-reloading.</p></div>")
+ var templ_7745c5c3_Var41 = []any{"grid grid-cols-[29%_1fr] gap-x-8 flex-1 min-h-0", templ.KV("gap-y-4", !false)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var41...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<div class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var42 string
+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var41).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"><div class=\"flex flex-col min-h-0 [--border-py:0.75rem]\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var43 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var44 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<div class=\"flex items-center gap-4 h-38\"><img src=\"/public/favicon.svg\" alt=\"picture\" class=\"h-full object-cover\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var45 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ var templ_7745c5c3_Var46 string
+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(fingerprint)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 161, Col: 21}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = CopyPre(sshKey, "shrink-0 text-[0.675rem] leading-[normal]").Render(templ.WithChildren(ctx, templ_7745c5c3_Var45), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Profile", "", "[--border-py:0.875rem]").Render(templ.WithChildren(ctx, templ_7745c5c3_Var44), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var47 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "contact@kyren.codes")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorderWithCopy("Email", "", "", "contact@kyren.codes", "mailto:contact@kyren.codes", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var47), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var48 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var49 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "git.kyren.codes")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Link("https://git.kyren.codes", true).Render(templ.WithChildren(ctx, templ_7745c5c3_Var49), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Git Repos (main)", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var48), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var50 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var51 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "github.com/Kyren223")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Link("https://github.com/kyren223", true).Render(templ.WithChildren(ctx, templ_7745c5c3_Var51), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("GitHub (secondary)", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var50), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var52 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var53 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "Kyren223")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Link("https://discord.com/users/481884415090229258", true).Render(templ.WithChildren(ctx, templ_7745c5c3_Var53), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Discord", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var52), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalStack(true, "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var43), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</div><div class=\"flex flex-col gap-10 min-h-0 select-text\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var54 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<div class=\"flex gap-[1ch] text-[1rem]\"><span>Working on</span> <span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var55 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "Krypton")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Link("/projects/krypton", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var55), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</span> <span>|</span> <span>Learning Langdev & Compiler Design</span> <span>|</span> <span>Available for hire</span></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Status", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var54), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var56 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "Hi! I'm Kyren, a softwarea engineer and game developer. I love working with low level languages and learning things deeply. I am also a terminal nerd and Linux enthusiast who uses NVIM btw. And when I am not coding, I am most likely playing video games, watching Anime, or solving Rubik's cubes fast.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("About Me", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var56), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = BlogItem("Latest Blog Post", *latestBlog, "").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var57 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ var templ_7745c5c3_Var58 string
+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(sshKey)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 210, Col: 12}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorderWithCopy("SSH Key", "", footerClass(false), sshKey, "", true).Render(templ.WithChildren(ctx, templ_7745c5c3_Var57), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/").Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/").Render(templ.WithChildren(ctx, templ_7745c5c3_Var40), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -339,6 +1214,14 @@ func HomePage(style Style) templ.Component {
})
}
+func footerClass(connected bool) string {
+ style := "col-span-2 select-text [--border-py:0.5rem]"
+ if connected {
+ style += " mb-[0.30rem]"
+ }
+ return style
+}
+
func SkillsPage(style Style) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -355,12 +1238,12 @@ func SkillsPage(style Style) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var16 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var16 == nil {
- templ_7745c5c3_Var16 = templ.NopComponent
+ templ_7745c5c3_Var59 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var59 == nil {
+ templ_7745c5c3_Var59 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var17 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var60 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@@ -372,13 +1255,57 @@ func SkillsPage(style Style) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h1 class=\"text-3xl font-bold\">About</h1><p>Single-binary web app using Go's standard library net/http.</p>")
+ templ_7745c5c3_Var61 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var62 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<span>$ fastfetch")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = Fastfetch(fetchRows).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Setup", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var62), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalStack(false, "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var61), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/skills.md").Render(templ.WithChildren(ctx, templ_7745c5c3_Var17), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/skills.md").Render(templ.WithChildren(ctx, templ_7745c5c3_Var60), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -402,12 +1329,452 @@ func ProjectsPage(style Style, projects []Project) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var18 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var18 == nil {
- templ_7745c5c3_Var18 = templ.NopComponent
+ templ_7745c5c3_Var63 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var63 == nil {
+ templ_7745c5c3_Var63 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var64 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<h1>Items</h1><div class=\"card\"><ul><li><span>foo</span> <span class=\"badge-active\">[ACTIVE]</span></li></ul></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/projects").Render(templ.WithChildren(ctx, templ_7745c5c3_Var64), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func NotFoundPage(style Style, path string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var65 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var65 == nil {
+ templ_7745c5c3_Var65 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var66 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var67 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var68 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<span><p>$ cd ~")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var69 string
+ templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(path)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 256, Col: 20}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</p><p class=\"pt-3\">\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var70 string
+ templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(path[1:])
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 257, Col: 32}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\": No such file or directory (os error 2) </p></span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Shell", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var68), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var71 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<div class=\"gap-[1ch]\"><span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var72 string
+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(">")
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 262, Col: 16}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</span> <span class=\"text-(--secondary)\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var73 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ var templ_7745c5c3_Var74 string
+ templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs("cd $HOME")
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 265, Col: 19}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Link("/", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var73), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</span></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("", "", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var71), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalStack(true, "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var67), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalLayout(style, "404").Render(templ.WithChildren(ctx, templ_7745c5c3_Var66), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func ScrollBox(extra string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var75 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var75 == nil {
+ templ_7745c5c3_Var75 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var76 = []any{"flex flex-1 min-h-0", extra}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var76...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<div data-scrollbox class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var77 string
+ templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var76).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var77)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\"><div data-scroll-area class=\"flex-1 min-w-0 overflow-y-auto border-[0.1rem] border-solid border-(--alt) p-[1rem] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var75.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "</div><div data-scroll-track class=\"relative w-8 -ml-[0.1rem] border-[0.1rem] border-solid border-(--alt)\"><div data-scroll-thumb class=\"absolute inset-x-[0.3rem] bg-(--secondary) cursor-grab touch-none\"></div></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func BlogsPage(style Style, blogs []Blog) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var78 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var78 == nil {
+ templ_7745c5c3_Var78 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var79 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var80 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "$ ls -lh ~/blogs")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalBorder("Shell", "", "mb-[1rem]").Render(templ.WithChildren(ctx, templ_7745c5c3_Var80), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var81 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var82 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ for i := 0; i < 1; i++ {
+ for _, blog := range blogs {
+ templ_7745c5c3_Err = BlogItem("", blog, "mb-[1rem]").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalStack(false, "mt-[0.5rem]").Render(templ.WithChildren(ctx, templ_7745c5c3_Var82), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = ScrollBox("").Render(templ.WithChildren(ctx, templ_7745c5c3_Var81), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = TerminalLayout(style, "blogs").Render(templ.WithChildren(ctx, templ_7745c5c3_Var79), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+type FetchRow struct {
+ Icon templ.Component
+ Color string // tailwind text color class, used for the icon and label
+ Label string
+ Value string
+}
+
+func IconMonitor(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var83 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var83 == nil {
+ templ_7745c5c3_Var83 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var84 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"></rect> <line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"></line> <line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"></line>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var84), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconTerminal(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var85 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var85 == nil {
+ templ_7745c5c3_Var85 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var19 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var86 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@@ -419,13 +1786,542 @@ func ProjectsPage(style Style, projects []Project) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<h1>Items</h1><div class=\"card\"><ul><li><span>foo</span> <span class=\"badge-active\">[ACTIVE]</span></li></ul></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<polyline points=\"4 17 10 11 4 5\"></polyline> <line x1=\"12\" x2=\"20\" y1=\"19\" y2=\"19\"></line>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = TerminalLayout(style, "/home/kyren/projects").Render(templ.WithChildren(ctx, templ_7745c5c3_Var19), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var86), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconCode(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var87 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var87 == nil {
+ templ_7745c5c3_Var87 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var88 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<polyline points=\"16 18 22 12 16 6\"></polyline> <polyline points=\"8 6 2 12 8 18\"></polyline>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var88), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconCpu(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var89 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var89 == nil {
+ templ_7745c5c3_Var89 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var90 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "<rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"></rect> <rect width=\"6\" height=\"6\" x=\"9\" y=\"9\" rx=\"1\"></rect> <path d=\"M15 2v2M15 20v2M2 15h2M2 9h2M20 15h2M20 9h2M9 2v2M9 20v2\"></path>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var90), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconScriptLang(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var91 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var91 == nil {
+ templ_7745c5c3_Var91 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var92 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-file-terminal preview-icon\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"></path><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"></path><path d=\"m8 16 2-2-2-2\"></path><path d=\"M12 18h4\"></path></svg>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var92), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconEditor(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var93 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var93 == nil {
+ templ_7745c5c3_Var93 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var94 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-square-pen preview-icon\"><path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path><path d=\"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z\"></path></svg>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var94), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconDebugger(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var95 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var95 == nil {
+ templ_7745c5c3_Var95 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var96 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-bug-off preview-icon\"><path d=\"M12 20v-8\"></path><path d=\"M12.656 7H14a4 4 0 0 1 4 4v1.344\"></path><path d=\"M14.12 3.88 16 2\"></path><path d=\"M17.123 17.123A6 6 0 0 1 6 14v-3a4 4 0 0 1 1.72-3.287\"></path><path d=\"m2 2 20 20\"></path><path d=\"M21 5a4 4 0 0 1-3.55 3.97\"></path><path d=\"M22 13h-3.344\"></path><path d=\"M3 21a4 4 0 0 1 3.81-4\"></path><path d=\"M3 5a4 4 0 0 0 3.55 3.97\"></path><path d=\"M6 13H2\"></path><path d=\"m8 2 1.88 1.88\"></path><path d=\"M9.712 4.06A3 3 0 0 1 15 6v1.13\"></path></svg>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var96), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconKeyboard(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var97 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var97 == nil {
+ templ_7745c5c3_Var97 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var98 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-keyboard preview-icon\"><path d=\"M10 8h.01\"></path><path d=\"M12 12h.01\"></path><path d=\"M14 8h.01\"></path><path d=\"M16 12h.01\"></path><path d=\"M18 8h.01\"></path><path d=\"M6 8h.01\"></path><path d=\"M7 16h10\"></path><path d=\"M8 12h.01\"></path><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"></rect></svg>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var98), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func IconUptime(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var99 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var99 == nil {
+ templ_7745c5c3_Var99 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var100 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-clock preview-icon\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><path d=\"M12 6v6l4 2\"></path></svg>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Icon(color).Render(templ.WithChildren(ctx, templ_7745c5c3_Var100), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func row(icon func(color string) templ.Component, color, label, value string) FetchRow {
+ return FetchRow{Icon: icon(color), Color: color, Label: label, Value: value}
+}
+
+var fetchRows = []FetchRow{
+ // row(IconTerminal, "text-(--secondary)", "Terminal", "Ghostty & Zsh"),
+ row(IconCode, "text-(--accent)", "Main Language", "C"),
+ row(IconScriptLang, "text-(--accent)", "Scripting Langs", "Go and Python"),
+ row(IconDebugger, "text-[#F16265]", "Debugger", "Raddebugger"),
+ row(IconMonitor, "text-[#54D7A9]", "Operating System", "Fedora KDE Linux"),
+ row(IconEditor, "text-[#54D7A9]", "Text Editor", "Neovim"),
+ row(IconUptime, "text-[#D8C44B]", "Uptime", GetMyUptime()),
+ // row(IconCpu, "text-(--extra)", "Hardware", "..."),
+}
+
+func branch(i, n int) string {
+ switch {
+ case n == 1:
+ return "─"
+ case i == 0:
+ return "┌"
+ case i == n-1:
+ return "└"
+ default:
+ return "├"
+ }
+}
+
+func Fastfetch(rows []FetchRow) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var101 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var101 == nil {
+ templ_7745c5c3_Var101 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<div style=\"\n\t\tdisplay: grid;\n\t\tgrid-template-columns: auto auto auto 1fr;\n\t\talign-items: center;\n\t\tcolumn-gap: 1ch;\n\t\tline-height: normal;\n\t\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for i, row := range rows {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<span class=\"text-(--alt)\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var102 string
+ templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(branch(i, len(rows)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 408, Col: 52}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = row.Icon.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var103 = []any{row.Color, "font-bold"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var103...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<span class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var104 string
+ templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var103).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var104)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var105 string
+ templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.JoinStringErrs(row.Label)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 410, Col: 53}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var105))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</span> <span id=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var106 string
+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(row.Label)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 411, Col: 23}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var107 string
+ templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.JoinStringErrs(row.Value)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 411, Col: 37}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var107))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func Icon(color string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var108 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var108 == nil {
+ templ_7745c5c3_Var108 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var109 = []any{"shrink-0", color}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var109...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<svg width=\"1.1em\" height=\"1.1em\" class=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var110 string
+ templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var109).String())
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `website.templ`, Line: 1, Col: 0}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var110)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var108.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "</svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}