Updating my personal page always felt like a hassle, so I tried to make it easier.
This project turns Markdown
files into a personal site with minimal effort. No manual HTML updates or overcomplicated CMS - just local markdown
(straight from my Obsidian vault, but any source works), a script to fetch and process it, and the site builds itself using Vue
and unplugin-vue-markdown
. Clean, simple, and (I hope!) maintainable.
Below is the structure for some sections of my page. I've kept things simple and easy to update while ensuring clarity for both content and future edits.
## ## Outline {#outline}
### Still Sketching
A bit of an intro about yourself.
### In the margins
A section for contacts or personal interests, hobbies, and other things you enjoy doing in your free time or that define who you are.
- Skill 1
- Skill 2
- Skill 3
- Skill 4
- Etc...
::: details Markdown Page
- #Skill1
- #Skill2
- #Skill3
- Etc...
[Github link](some-link){target="\_blank" rel="noopener noreferrer"}
A brief description about the project and/or skill.
:::
I wanted the page to be fully responsive, supporting a minimum width of 320px
(though it can technically go a bit lower, I didn’t optimize for anything below that). At the same time, I aimed to meet AA
accessibility standards by ensuring proper contrast, natural keyboard navigation, and screen reader-friendly properties from the start. Throughout this process, I picked up a few interesting insights:
Since my goal was to replicate the editing experience I have in Obsidian as closely as possible, I integrated some markdown-it
plugins (which unplugin-vue-markdown
relies on under the hood):
@mdit/plugin-attrs
: Enables custom attributes likeid
,class
, and native HTML properties directly in markdown.## Heading {#custom-id .custom-class data-info="extra"}
@mdit/plugin-container
: Adds support for custom containers, which I primarily use for<details>
elements to organize content like project descriptions.::: details Some title This would be hidden content inside a `<details>` tag. :::
@mdit/plugin-tasklist"
: Enables GitHub-style task lists using- [ ]
syntax.- [ ] Task example one - [x] Task example two
This was a super handy reminder!
I’d played around with clamp()
before, but this was the first time I really put it to work and it made responsiveness much easier. Instead of drowning in @media
queries, I used clamp()
to handle both font sizes and (most) of spacing dynamically, allowing the page adapt more organically to different screen sizes and zoom levels.
I used the CSS counter
property to automatically number the details cards, which gave a nice touch without needing extra markup or JavaScript. Instead of relying on the default markers, I removed them and used the counter to add a dynamic number in front of each summary. It’s a simple trick, but it really cleaned things up a bit.
I worked mostly with native HTML5 form validation, using properties like minlength
, pattern
, and pseudo-classes like :invalid
and :valid
. These helped handle the basic validation without needing extra JavaScript. The only time I used JavaScript was for setCustomValidity()
when the input was empty, as the default validation wouldn’t trigger an error for that case. It was interesting to see how HTML5’s native form validation features continue to improve, making form validation a bit less tricky.
Not a fuzzy search, just a slightly more flexible way to match skills by normalizing input and checking against skill names. Here’s the logic:
- Cleans up input: Lowercases, removes non-alphabetic chars, numbers, and trims "js"/"ts" suffixes with regex. No weird edge cases allowed.
- Finds matches: Uses
includes
to catch terms anywhere in the skill name. - Trims the fat: Prioritizes shorter matches and only keeps the shortest ones. This avoids searching for
react
and getting an overloaded list likeReact/React Query/React Router
, ensuring the simplest match is returned first. - Handles no matches: If nothing fits, the input goes into a
noMatch
array.
For real fuzzy search, I could’ve used a library or implemented Damerau-Levenshtein (or some other fancy algorithm), but for a simple project like this, keeping it lightweight and to the point just made more sense.
- Accessibility: I’ve tried to build in as much accessibility support as I could from the get-go, but I’m sure there’s still a lot of potential to improve. I’ve probably missed a few things here and there, so I’ll be sure to revisit this as an opportunity to learn a bit more.
- Smooth out
details
toggle: I held off on adding an animation to thedetails
section since it’s not supported across all browsers. Sure, I could have hacked something together, but I’d rather wait until cross-browser compatibility is more reliable before diving in. - Turn it into a more automatic CMS: At the moment, I’m still building locally and manually deploying to Netlify, which feels a bit… old-school. It would be much smoother if I just pushed updates to the repository, and let Netlify handle the auto-deploys.
- Add Theming: Wouldn’t it be fun to throw in some theming functionality? I’ve thought about it - switching up the looks to keep things fresh and adaptable.
- Fuzzy Search: As much as this basic search function does the job, I think it could be improved. A proper fuzzy search using something like Damerau-Levenshtein (not trying to reinvent the wheel, but learning) would give the search feature some real power.
- Unit Tests: I opted for speed over thorough testing this time around (whoops), but unit tests are definitely on my radar for the future.
- Visual Testing: Same story here with visual testing, didn’t prioritize it because of the time crunch. But it’s a must-do for later, especially when it comes to consistent design across different screen sizes.
- CI/CD: Setting up proper GitHub Actions workflows is next on the list. That’ll streamline the process a bit, and save me a bunch of time, especially when paired with unit testing, visual testing, linting, and formatting. Much better than manually wrangling through everything!