custom shiki highlighter from dominikg
This commit is contained in:
parent
c7c2ea60a2
commit
9b976986d7
|
@ -1,6 +1,7 @@
|
|||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
|
|
|
@ -24,6 +24,12 @@ body {
|
|||
src: url('/fonts/Quicksand.ttf');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.prose {
|
||||
font-family: 'Cooper Hewitt', sans-serif;
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
|
||||
/*
|
||||
@font-face {
|
||||
font-family: 'Magilio';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
tldr:
|
||||
I started by containerizing my SvelteKit and Strapi apps, then Pushed these to Docker Hub and AWS ECR,
|
||||
leveraging 1 free private image on docker hub and free-tier 500mb limit on AWS ECR, thereby minimizing costs and exploring each option.
|
||||
|
||||
1. Conteinerization
|
||||
a. Sveltekit
|
||||
Keeping in mind that dev, build, test and lint, etc. scripts are handled by turborepo, we use these when containerizing.
|
||||
|
||||
Step 1.
|
||||
|
||||
b. Strapi
|
|
@ -0,0 +1,7 @@
|
|||
choosing a package manager
|
||||
|
||||
I tried to abstain from the community battles between pms and looked at the performance of the top 3 used - npm, yarn and pnpm. https://pnpm.io/benchmarks
|
||||
I've got experience with each and would like to use pnpm as it is the much faster than NPM,
|
||||
however Strapi doesn't officialy support it so I ended up choosing yarn as in most cases it is a bit faster than npm, has some better handling with monorepos and I personally like it.
|
||||
I used Turborepo with npm and there were some problems with setting dependencies in workspaces vs the root of monorepo and private packages, which required workarounds.
|
||||
In terms of CI performance can have significant consequences.
|
|
@ -0,0 +1,15 @@
|
|||
AWS set up
|
||||
|
||||
A thing I learned using linux is user management, specifically don't do everything as root :D.
|
||||
When using AWS first I didn't care about IAM roles, but I think I learned the same lesson...
|
||||
|
||||
Anyways I used this process, when configuring accounts:
|
||||
|
||||
When starting login as root user and access IAM dashboard,
|
||||
Set up Multi-factor Auth as root user (prefferably with Yubikey or some OTP in a secure device)
|
||||
Later on, like in linux, the root account access can be minimized for a better decentralized privilege system, even when being the only admin.
|
||||
|
||||
Go to services then IAM Identity center
|
||||
Select identity source -> In my case stick with the default Identity Center directory, bigger organizations or ones using other sources can connect to external ones.
|
||||
In Multi-account permissions select permission sets and create a permission set.
|
||||
Then go to AWS accounts, select the account you wish and assign the permission set to them.
|
|
@ -0,0 +1,62 @@
|
|||
Namecheap
|
||||
Saw mattmor.in and just bought it, no questions asked.
|
||||
|
||||
I immediately created a cloudflare account, because of their great certificate management, security checks, analytics and mostly DNS.
|
||||
|
||||
|
||||
|
||||
|
||||
Setting up AWS EC2 & RDS instances
|
||||
|
||||
EC2 setup
|
||||
I've got a great opportunity to set up a "free-tier" instance, of course the limitations are quite interesting - 750 hrs/m and some compute limiting factors.
|
||||
Let's use this opportunity, it is a better deal than GCP with 300$/3mos and my experience with GCP while dealing with Auth API was pretty bad.
|
||||
|
||||
Made SSH keys
|
||||
got an Elastic IP and connected it with my EC2 instance to be accessible publicly on a stable IP.
|
||||
added DNS records to cloudflare
|
||||
|
||||
|
||||
Log in via SSH, update, upgrade everything, restart.
|
||||
Then I configured fail2ban with some custom responses and enabled it, I also configured the seemingly redundant ufw and allowed ssh, https
|
||||
|
||||
And with that I am ready to roll onto installing necessary software like nginx, jenkins, docker for now.
|
||||
|
||||
RDS setup
|
||||
|
||||
I've already configured Strapi for my backend, I selected PostgreSQL, because while theoretically the default SQLite db would be sufficient and very easy to manage.
|
||||
I know it's scalability is limited and is not suitable for multiple user access, which I might need in other projects with the same tech stack.
|
||||
The main reason for choosing PostgreSQL is I can showcase it and learn with it on a project using AWS.
|
||||
I've worked mainly with MariaDB before, and used PostgreSQL only on some ancient Wordpress projects back in the day.
|
||||
|
||||
As I already set up a free-tier EC2 instance, I can also setup AWS RDS with Aurora(PostgreSQL compatible) or PostgreSQL also on free-tier, which is awesome!
|
||||
Let's choose PostgreSQL, because that's simpler and more than sufficient.
|
||||
|
||||
The Free Tier is limited to the same time frame for RDS...
|
||||
If I really wanted to make this completely free I could use AWS Lambda, which is also free up to 1 million requests/month :D, to turn off both the EC2 and RDS instances at night. Gotta think when do recruiters go to sleep...
|
||||
|
||||
|
||||
Now seriously, to actualy set this up:
|
||||
|
||||
Select:
|
||||
burstable class db.t4g.micro
|
||||
General Purpose SSD (gp3) 20GiB
|
||||
I disabled autoscaling as it's not possible I would use more than this in this use-case.
|
||||
|
||||
Strapi recommends PostgreSQL v14.0 at the time of writing
|
||||
AWS offers 14.5 above, let's choose 14.9 R1, should be backwards compatible.
|
||||
Template -> Free tier
|
||||
|
||||
Security:
|
||||
As my OpSec dictates I choose passwords with high entropy so you can't hack me.
|
||||
|
||||
Backup & Maintenance:
|
||||
Setup Backup and Amazon auto-maintenance windows, I am not a maniac to not backup.
|
||||
|
||||
Connectivity:
|
||||
I will not connect this to my EC2 instance, because I will be testing this on localhost.
|
||||
I need public access and in this case I do not think accessing by a private network is necessary.
|
||||
I will make a new VPC security group, then we can access it via an internet gateway.
|
||||
|
||||
|
||||
https://docs.aws.amazon.com/images/AmazonRDS/latest/UserGuide/images/GS-VPC-network.png
|
|
@ -1,18 +1,26 @@
|
|||
---
|
||||
title: First post
|
||||
type: blog
|
||||
excerpt: First post
|
||||
date: 2021-01-01
|
||||
tags:
|
||||
- first
|
||||
- post
|
||||
published: true
|
||||
- Blog
|
||||
- Linux
|
||||
- Declarative
|
||||
image: Feature.jpg
|
||||
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||
focusKeyphrase: 'best medium format camera'
|
||||
datePublished: '2023-04-07T16:04:42.000+0100'
|
||||
lastUpdated: '2023-04-14T10:17:52.000+0100'
|
||||
seoMetaDescription: "Let's take a look."
|
||||
featuredImage: ''
|
||||
featuredImageAlt: 'Our own pcb'
|
||||
publicImageId: ''
|
||||
---
|
||||
## Svelte
|
||||
|
||||
Media inside the **Svelte** folder is served from the `static` folder.
|
||||
|
||||
```python
|
||||
```python showLines
|
||||
|
||||
input_text = ''' "yahooapis.com",
|
||||
"hotmail.com",
|
||||
|
@ -30,3 +38,112 @@ output_text = '\n'.join(formatted_lines)
|
|||
print(output_text)
|
||||
|
||||
```
|
||||
|
||||
## Turborepo [see 1st source]
|
||||
I will use turborepo as I have experience with it from previous projects, I prefer the centralized, more orderly handling of repositories.
|
||||
Because our CMS and webapp are workspaces that need to be configured differently, we need to extend the root config of Turborepo to each app individually by creating a turbo.json in each workspace.
|
||||
|
||||
For Strapi this will be:
|
||||
$root/apps/cms/turbo.json
|
||||
|
||||
``` json
|
||||
|
||||
{
|
||||
"extends": ["//"],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
// custom configuration for the build task in this workspace
|
||||
},
|
||||
// new tasks only available in this workspace
|
||||
"special-task": {},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Sveltekit Webapp:
|
||||
$root/apps/web/turbo.json
|
||||
|
||||
``` json
|
||||
{
|
||||
"extends": ["//"],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"outputs": [".svelte-kit/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key notes about this setup:
|
||||
|
||||
Docker:
|
||||
Base Layer: Installs container dependencies and Turborepo globally.
|
||||
Pruned Layer: Copies project files and runs turbo prune to exclude unnecessary dependencies.
|
||||
Installer Layer: Copies pruned workspace and runs yarn to install dependencies.
|
||||
Runner Layer: Starts the app.
|
||||
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
|
||||
Learned
|
||||
|
||||
Used sources:
|
||||
|
||||
1. https://turbo.build/repo/docs
|
||||
2. https://dev.to/moofoo/creating-a-development-dockerfile-and-docker-composeyml-for-yarn-122-monorepos-using-turborepo-896
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
## Docker
|
||||
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
install Grafana, Prometheus, ELK Stack, and Jenkins on a single server and use them to monitor other EC2 instances or cloud resources. There are trade-offs:
|
||||
|
||||
Pros:
|
||||
|
||||
Simplified Management: All tools in one place.
|
||||
Lower Costs: Fewer servers to maintain.
|
||||
|
||||
Cons:
|
||||
|
||||
Resource Contention: These tools can be resource-intensive.
|
||||
Single Point of Failure: If the server goes down, all tools are affected.
|
||||
Security Risks: Multiple services on one server can increase the attack surface.
|
||||
|
||||
Recommendations:
|
||||
|
||||
Use containerization (Docker) for easier management and isolation.
|
||||
Set up a robust backup and recovery strategy.
|
||||
Ensure adequate resource allocation and scaling capabilities.
|
|
@ -1,12 +1,19 @@
|
|||
---
|
||||
title: w post
|
||||
excerpt: w post
|
||||
date: 2021-01-01
|
||||
title: First post
|
||||
type: blog
|
||||
excerpt: First post
|
||||
tags:
|
||||
- first
|
||||
- post
|
||||
published: true
|
||||
- NixOS
|
||||
- Linux
|
||||
- Declarative
|
||||
image: Feature.jpg
|
||||
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||
focusKeyphrase: 'best medium format camera'
|
||||
datePublished: '2022-04-07T16:04:42.000+0100'
|
||||
lastUpdated: '2022-04-14T10:17:52.000+0100'
|
||||
seoMetaDescription: "Let's take a look."
|
||||
featuredImage: ''
|
||||
featuredImageAlt: 'Our own pcb'
|
||||
publicImageId: ''
|
||||
---
|
||||
|
||||
## Svelte
|
||||
## Svelte
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
The goal of this project is to showcase my skillset both as a portfolio site and showcase how I used devops and web development practices to build it.
|
||||
The tech stack and feature set is meant to:
|
||||
legitimize the goal of the portfolio site, which is mentioned above
|
||||
Use modern DevOps and web development practices that I know of so I can build it as fast as possible.
|
||||
minimize costs and be potentially scalable and feature-upgradeable.
|
||||
be secure and be build using my evolving OpSec and limited cybersecurity knowledge.
|
||||
show proficiency in using new tools.
|
||||
|
||||
As such the current cloud stack is:
|
||||
1. AWS EC2 t3.micro
|
||||
For: Jenkins
|
||||
Why: CI/CD pipeline, free tier, 1GB RAM sufficient for Jenkins.
|
||||
2. AWS EC2 t3.micro
|
||||
For: docker-compose SvelteKit app
|
||||
Why: Frontend, free tier, 1GB RAM sufficient for SvelteKit.
|
||||
|
||||
3. Amazon ECS Anywhere
|
||||
|
||||
For: docker-compose Strapi
|
||||
Why: Container orchestration, 2200 free hours, scalable.
|
||||
|
||||
4. AWS Lambda
|
||||
|
||||
For: Automated tasks
|
||||
Why: 1 million free requests, event-driven architecture.
|
||||
|
||||
5. Amazon RDS
|
||||
|
||||
For: Database
|
||||
Why: 750 free hours, managed service, 20GB storage.
|
||||
|
||||
6. Amazon S3
|
||||
|
||||
For: File storage, backups
|
||||
Why: 5GB free storage, durable.
|
||||
|
||||
7. Amazon CloudWatch
|
||||
|
||||
For: Monitoring
|
||||
Why: 10 free custom metrics and alarms.
|
||||
|
||||
8. AWS Secrets Manager
|
||||
|
||||
For: Secrets
|
||||
Why: Secure, but consider alternatives due to cost.
|
||||
|
||||
9. Amazon API Gateway
|
||||
|
||||
For: APIs
|
||||
Why: 1 million free API calls, secure.
|
||||
|
||||
10. Terraform and Ansible
|
||||
|
||||
For: IaC and Configuration
|
||||
Why: Version control, automation.
|
||||
|
||||
11. Documentation
|
||||
|
||||
For: READMEs
|
||||
Why: Clarity, onboarding, and best practices.
|
||||
|
||||
12. GitHub and AWS ECR
|
||||
|
||||
For: Code and container repositories
|
||||
Why: Version control, Docker Hub limitations.
|
||||
|
||||
Cost Analysis
|
||||
|
||||
EC2 t3.micro: Free tier
|
||||
ECS Anywhere: Free tier (2200 hours)
|
||||
Lambda: Free tier (1 million requests)
|
||||
RDS: Free tier (750 hours)
|
||||
S3: Free tier (5GB)
|
||||
CloudWatch: Free tier (10 metrics)
|
||||
Secrets Manager: $0.40/secret, consider alternatives
|
||||
API Gateway: Free tier (1 million calls)
|
|
@ -1,13 +1,20 @@
|
|||
---
|
||||
title: Second post
|
||||
excerpt: Second post
|
||||
date: 2023-01-01
|
||||
title: First post
|
||||
type: blog
|
||||
excerpt: First post
|
||||
tags:
|
||||
- first
|
||||
- post
|
||||
published: true
|
||||
- NixOS
|
||||
- Linux
|
||||
- Declarative
|
||||
image: Feature.jpg
|
||||
|
||||
postTitle: 'Best Medium Format Camera for Starting Out'
|
||||
focusKeyphrase: 'best medium format camera'
|
||||
datePublished: '2024-04-07T16:04:42.000+0100'
|
||||
lastUpdated: '2021-04-14T10:17:52.000+0100'
|
||||
seoMetaDescription: "Let's take a look."
|
||||
featuredImage: ''
|
||||
featuredImageAlt: 'Our own pcb'
|
||||
publicImageId: ''
|
||||
---
|
||||
|
||||
|
||||
|
@ -23,5 +30,4 @@ Media inside the **Svelte** folder is server from the `static` folder.
|
|||
|
||||
I am ditching the societal value of having a contributions table on my profile, you should view it on git.mattmor.in
|
||||
|
||||
|
||||
--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore.
|
||||
--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore.
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
Hard skills in SW:
|
||||
HTML, CSS, JS, TS, Svelte, Sveltekit, Vite,
|
||||
|
||||
WebDev (meta)frameworks: Svelte (and sveltekit), a theoretical knowledge of reactjs (and next.js) and vue (and nuxt)
|
||||
WebDev langs: HTML, CSS, tailwindcss, postcss (basics), JS, TS (basics), multiple ui libraries and billions of packages :D
|
||||
WebDev linting: eslint
|
||||
WebDev testing: Playwright
|
||||
|
||||
Languages: JS, Python, Micropython, C and C++ (both basics with microcontrollers), I really want to code in OstraJava and Brainf*ck
|
||||
|
||||
|
||||
|
||||
OS:
|
||||
Linux [Ubuntu, Debian (Rpi OS, Kali - basics), QubesOS, Arch (Manjaro)], WSL,
|
||||
Windows :D, advanced as a power user of XP,Vista,7,8,10,11 in my life, caused deep trauma. Haven't used win servers and don't plan to.
|
||||
|
||||
Terminal:
|
||||
Process Monitoring
|
||||
Performance Monitoring
|
||||
Networking Tools
|
||||
Text Manipulation
|
||||
|
||||
Scripting:
|
||||
Bash
|
||||
Power Shell (for user tasks)
|
||||
|
||||
Editors:
|
||||
Nano, Emacs, VS Code, Notepad :D and Jupyter notebook
|
||||
|
||||
|
||||
|
||||
Version control: Git
|
||||
|
||||
|
||||
VCS Hosting: Github, Gitlab
|
||||
CI/CD:
|
||||
Jenkins In progress
|
||||
Gitlab CI In progress
|
||||
|
||||
Infrastructure Provisioning:
|
||||
Terraform: In progress
|
||||
|
||||
Cloud Providers:
|
||||
AWS - Preffered
|
||||
DigitalOcean
|
||||
Google Cloud (I did auth, api, company set up and some bots with spreadsheet)
|
||||
|
||||
CDN: Cloudinary
|
||||
|
||||
Networking, Security and Protocols:
|
||||
FTP / SFTP
|
||||
SSL / TLS
|
||||
HTTP / HTTPS
|
||||
DNS
|
||||
SSH (putty and linux)
|
||||
|
||||
Serverless: AWS Lambda (0), Cloudflare, Vercel
|
||||
|
||||
Monorepo (with pipelines): all yarn, npm, pnpm with turborepo
|
||||
|
||||
|
||||
|
||||
Containerization: docker (dockerfile, docker-compose, ran many apps with it), DockerHub, AWS RCD
|
||||
|
||||
Container Orchestration: Docker swarm(basics)
|
||||
K8s/K3s: In progress...
|
||||
|
||||
Orchestration: Terraform (0)
|
||||
|
||||
GitOps: In progress ArgoCD
|
||||
|
||||
Application monitoring: New Relic, Prometheus, Grafana, Elk stack
|
||||
|
||||
Logs Management:
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
## Turborepo [see 1st source]
|
||||
I will use turborepo as I have experience with it from previous projects, I prefer the centralized, more orderly handling of repositories.
|
||||
Because our CMS and webapp are workspaces that need to be configured differently, we need to extend the root config of Turborepo to each app individually by creating a turbo.json in each workspace.
|
||||
|
||||
For Strapi this will be:
|
||||
$root/apps/cms/turbo.json
|
||||
```
|
||||
{
|
||||
"extends": ["//"],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
// custom configuration for the build task in this workspace
|
||||
},
|
||||
// new tasks only available in this workspace
|
||||
"special-task": {},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Sveltekit Webapp:
|
||||
$root/apps/web/turbo.json
|
||||
```
|
||||
{
|
||||
"extends": ["//"],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"outputs": [".svelte-kit/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Key notes about this setup:
|
||||
|
||||
Docker:
|
||||
Base Layer: Installs container dependencies and Turborepo globally.
|
||||
Pruned Layer: Copies project files and runs turbo prune to exclude unnecessary dependencies.
|
||||
Installer Layer: Copies pruned workspace and runs yarn to install dependencies.
|
||||
Runner Layer: Starts the app.
|
||||
|
||||
## Docker
|
||||
Alpine is more lightweight than Ubuntu, I will stick to the inspiration blog post [see 2nd source], which has a similar setup.
|
||||
The blog post creates intermediate images 'base', 'pruned' etc. that can be used in subsequent stages.
|
||||
|
||||
To push images to docker hub:
|
||||
|
||||
```docker push mando42/portfolio:tagname```
|
||||
|
||||
|
||||
Learned
|
||||
|
||||
Used sources:
|
||||
1. https://turbo.build/repo/docs
|
||||
2. https://dev.to/moofoo/creating-a-development-dockerfile-and-docker-composeyml-for-yarn-122-monorepos-using-turborepo-896
|
|
@ -0,0 +1,40 @@
|
|||
.code-highlight {
|
||||
position: relative;
|
||||
font-family: 'Fira Mono', monospace;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 60vh;
|
||||
margin: 1rem auto;
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.code-highlight > .code-language {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
background: rgba(127, 127, 127, 0.3);
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.code-highlight > code {
|
||||
display: inline-block;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.code-highlight > code.numbered {
|
||||
margin-left: 2rem;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 1px solid rgba(127, 127, 127, 0.5);
|
||||
counter-reset: line;
|
||||
}
|
||||
|
||||
.code-highlight > code > .line-of-code {
|
||||
white-space: pre-wrap;
|
||||
display: inline-block;
|
||||
min-height: 1em;
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
:root {
|
||||
--light: #f1f4f8;
|
||||
--dark: #2c2d2f;
|
||||
--primary: #ff3e00;
|
||||
--fg: var(--light);
|
||||
--bg: var(--dark);
|
||||
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||
--shadow: 0 0.5rem 0.5rem 0 rgba(0, 0, 0, 0.2), 0 0.25rem 1rem 0 rgba(0, 0, 0, 0.2),
|
||||
0 0.5rem 0.25rem -0.25rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@media only screen and (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--fg: var(--light);
|
||||
--bg: var(--dark);
|
||||
--color-scrollbar: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
body.light {
|
||||
--fg: var(--dark);
|
||||
--bg: var(--light);
|
||||
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--fg: var(--dark);
|
||||
--bg: var(--light);
|
||||
--color-scrollbar: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
body.dark {
|
||||
--fg: var(--light);
|
||||
--bg: var(--dark);
|
||||
--color-scrollbar: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 1.5rem;
|
||||
height: 1080px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
}
|
||||
|
||||
p,
|
||||
.prose {
|
||||
font-family: 'Cooper Hewitt', sans-serif;
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: calc(100vw - 2rem);
|
||||
max-width: 1920px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: currentColor;
|
||||
transition: color 200ms ease-in-out;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-scrollbar) transparent;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 0.5rem;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--color-scrollbar);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// src/lib/cloudinary.ts
|
||||
import { v2 as cloudinary } from 'cloudinary';
|
||||
import { PUBLIC_CLOUDINARY_NAME } from '$env/static/public';
|
||||
import { CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET } from '$env/static/private';
|
||||
|
||||
cloudinary.config({
|
||||
cloud_name: PUBLIC_CLOUDINARY_NAME,
|
||||
api_key: CLOUDINARY_API_KEY,
|
||||
api_secret: CLOUDINARY_API_SECRET
|
||||
});
|
||||
|
||||
export default cloudinary;
|
|
@ -1,26 +1,26 @@
|
|||
<section class="grid grid-cols-1 md:grid-cols-3 gap-4 lg:gap-8" data-svelte-h="svelte-1u9sn7t">
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-screwdriver-wrench text-4xl text-primary-500" />
|
||||
<h3 class="h3">Development</h3>
|
||||
<p class="opacity-75">
|
||||
I possess a wide range of skills that enable me to develop visually appealing and
|
||||
interactive user interfaces for web applications.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-palette text-4xl text-primary-500" />
|
||||
<h3 class="h3">Design</h3>
|
||||
<p class="opacity-75">
|
||||
Over the years, I have honed my ability to create visually appealing interfaces that
|
||||
are both user-friendly and intuitive.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-users text-4xl text-primary-500" />
|
||||
<h3 class="h3">User Experience</h3>
|
||||
<p class="opacity-75">
|
||||
I understand the importance of creating a seamless UX for end-users. Which includes
|
||||
a solid understanding user behavior.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-screwdriver-wrench text-4xl text-primary-500" />
|
||||
<h3 class="h3">Development</h3>
|
||||
<p class="opacity-75">
|
||||
I hone my problem solving and coding skills all the time. I try to learn the underlying
|
||||
mechanics and use abstractions where relevant.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-palette text-4xl text-primary-500" />
|
||||
<h3 class="h3">Creativity & Presentation</h3>
|
||||
<p class="opacity-75">
|
||||
I try to create better, novel ways to approach problems and give my best to present them
|
||||
in a clear and concise manner.
|
||||
</p>
|
||||
</div>
|
||||
<div class="card variant-ringed-hollow p-4 md:p-8 space-y-4">
|
||||
<i class="fa-solid fa-users text-4xl text-primary-500" />
|
||||
<h3 class="h3">Teamwork & Stakeholders</h3>
|
||||
<p class="opacity-75">
|
||||
From my experience with a wide range of roles I understand the how to communication with
|
||||
stakeholders across an organization.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { promises as fsp } from 'fs';
|
||||
|
||||
export async function makeDirectory(directoryPath) {
|
||||
fsp.mkdir(directoryPath, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
import { getHighlighter } from 'shiki';
|
||||
import { getTheme, loadTheme } from 'shiki-themes';
|
||||
|
||||
const escapeChars = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'{': '{',
|
||||
'}': '}'
|
||||
};
|
||||
|
||||
const escapeRE = new RegExp(`[${Object.keys(escapeChars).join('')}]`, 'g');
|
||||
|
||||
function escape(str) {
|
||||
if (str && str.length !== 0) {
|
||||
return str.replace(escapeRE, (c) => escapeChars[c]);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function render(lines, options) {
|
||||
const { fg, bg } = options;
|
||||
const lineNumbers = options.showLineNumbers(lines.length, options.lang);
|
||||
const lang = options.lang ? `<span class="code-language">${options.lang}</span>` : '';
|
||||
return `<pre class="code-highlight" style="color: ${fg}; background-color: ${bg}">${lang}<code class="${
|
||||
lineNumbers ? 'numbered' : 'simple'
|
||||
}">${lines.map(lineRenderer(options)).join('\n')}\n</code></pre>`;
|
||||
}
|
||||
|
||||
const lineRenderer = (options) => (line) => {
|
||||
const output = line.map(tokenRenderer(options)).join('');
|
||||
const { leadingWS, content, trailingWS } = splitLeadingAndTrailingWS(output);
|
||||
return `${leadingWS || ''}<span class="line-of-code">${content || ''}</span>${
|
||||
trailingWS || ''
|
||||
}`;
|
||||
};
|
||||
|
||||
const tokenRenderer = (options) => (token) => {
|
||||
if (!token.color || token.color.toLowerCase() === options.fg) {
|
||||
return escape(token.content);
|
||||
}
|
||||
const { leadingWS, content, trailingWS } = splitLeadingAndTrailingWS(token.content);
|
||||
if (!content) {
|
||||
return leadingWS;
|
||||
}
|
||||
|
||||
return `${leadingWS}<span style="color: ${token.color}">${escape(content)}</span>${trailingWS}`;
|
||||
};
|
||||
|
||||
function splitLeadingAndTrailingWS(content) {
|
||||
const len = content.length;
|
||||
let start = 0;
|
||||
let end = len;
|
||||
|
||||
while (start < end && isWS(content.charAt(start))) {
|
||||
start++;
|
||||
}
|
||||
|
||||
if (start === end) {
|
||||
return {
|
||||
leadingWS: content || ''
|
||||
};
|
||||
}
|
||||
|
||||
while (end > start && isWS(content.charAt(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return {
|
||||
leadingWS: content.slice(0, start),
|
||||
content: content.slice(start, end),
|
||||
trailingWS: end < content.length ? content.slice(end) : ''
|
||||
};
|
||||
}
|
||||
|
||||
function isWS(char) {
|
||||
return char === ' ' || char === '\t';
|
||||
}
|
||||
|
||||
function isPlaintext(lang) {
|
||||
return !lang || ['plaintext', 'txt', 'text'].indexOf(lang) !== -1;
|
||||
}
|
||||
|
||||
const defaultOpts = {
|
||||
theme: 'nord',
|
||||
fg: undefined,
|
||||
bg: undefined,
|
||||
showLineNumbers: (numberOfLines) => numberOfLines > 5
|
||||
};
|
||||
|
||||
export default async function createHighlighter(opts) {
|
||||
const options = { ...defaultOpts, ...opts };
|
||||
|
||||
if (options.theme.endsWith('.json')) {
|
||||
options.theme = loadTheme(options.theme);
|
||||
} else {
|
||||
options.theme = getTheme(options.theme);
|
||||
}
|
||||
const baseSettings = (
|
||||
(options.theme['tokenColors'] || []).find((x) => !x.scope) || { settings: {} }
|
||||
).settings;
|
||||
const colors = options.theme.colors || {};
|
||||
const getThemeColor = (name) => baseSettings[name] || colors[`editor.${name}`] || colors[name];
|
||||
const fg = (options.fg || getThemeColor('foreground') || '#eeeeee').toLowerCase();
|
||||
const bg = (options.bg || getThemeColor('background') || '#222222').toLowerCase();
|
||||
return getHighlighter(options).then(
|
||||
(highlighter) => (code, lang) =>
|
||||
render(
|
||||
isPlaintext(lang)
|
||||
? [[{ content: code }]]
|
||||
: highlighter.codeToTokensBase(code, lang),
|
||||
{ ...options, fg, bg, lang }
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,48 +1,26 @@
|
|||
<script lang="ts">
|
||||
import SkillContainer from '$lib/components/SkillContainer.svelte';
|
||||
import SkillContainer from '$lib/components/skills/SkillContainer.svelte';
|
||||
import HeroSection from '$lib/components/home/HeroSection.svelte';
|
||||
import QuickCards from '$lib/components/home/QuickCards.svelte';
|
||||
|
||||
|
||||
import * as conf from '$lib/config';
|
||||
import { website } from '$lib/config';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Matt Morin</title>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="description" content={conf.description} />
|
||||
<meta property="og:title" content={conf.title} />
|
||||
<meta name="description" content={website.description} />
|
||||
<meta property="og:title" content={website.siteTitle} />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:description" content={conf.description} />
|
||||
<meta property="og:url" content={conf.url} />
|
||||
<meta property="og:description" content={website.description} />
|
||||
<meta property="og:url" content={website.url} />
|
||||
<meta property="og:image" content="/images/profile-pic.png" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="mt-24 container h-full mx-auto flex-col justify-center items-center md:w-3/4">
|
||||
<!-- <div class="space-y-10 mt-4 text-center flex flex-col items-center">
|
||||
<h1 class="h1">I make the wheels turn.</h1>
|
||||
|
||||
<figure>
|
||||
<section class="img-bg" />
|
||||
<img
|
||||
src="/images/profile-pic.png"
|
||||
class="w-8 h-8 md:h-[200px] md:w-[200px]"
|
||||
alt="Profile picture"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
|
||||
<img
|
||||
src="/animations/infinity-loop-icon.svg"
|
||||
alt="Icon"
|
||||
class="w-16 md:w-32 lg:w-48 h-full rounded-full"
|
||||
/>
|
||||
<h2 class="h2">My github contributions</h2>
|
||||
</div> -->
|
||||
|
||||
<div class="mt-24 container h-full mx-auto flex-col justify-center items-center md:w-3/4 space-y-8">
|
||||
<HeroSection />
|
||||
|
||||
<SkillContainer />
|
||||
<QuickCards />
|
||||
|
||||
|
||||
<SkillContainer />
|
||||
</div>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 B |
Binary file not shown.
After Width: | Height: | Size: 147 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
@ -4,8 +4,9 @@ import forms from '@tailwindcss/forms';
|
|||
import typography from '@tailwindcss/typography';
|
||||
import { skeleton } from '@skeletonlabs/tw-plugin';
|
||||
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
const defineConfig: Config = {
|
||||
darkMode: 'media',
|
||||
|
||||
content: [
|
||||
'./src/**/*.{html,js,svelte,ts}',
|
||||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||
|
@ -27,4 +28,6 @@ export default {
|
|||
}
|
||||
})
|
||||
]
|
||||
} satisfies Config;
|
||||
};
|
||||
|
||||
export default defineConfig;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { threeMinifier } from '@yushijinhun/three-minifier-rollup';
|
||||
import { imagetools } from 'vite-imagetools';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
|
@ -30,7 +31,8 @@ export default defineConfig({
|
|||
greedy: [/^hljs-/]
|
||||
}
|
||||
}),
|
||||
{ ...threeMinifier(), enforce: 'pre' }
|
||||
{ ...threeMinifier(), enforce: 'pre' },
|
||||
imagetools({ removeMetadata: true })
|
||||
],
|
||||
define: {
|
||||
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString())
|
||||
|
|
Loading…
Reference in New Issue