diff --git a/src/app.d.ts b/src/app.d.ts index 8f4d638..7564bf3 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -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 {} diff --git a/src/app.postcss b/src/app.postcss index 2b81492..e279a82 100644 --- a/src/app.postcss +++ b/src/app.postcss @@ -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'; diff --git a/src/content/blog/Dockerizing apps.txt b/src/content/blog/Dockerizing apps.txt new file mode 100644 index 0000000..c90ec11 --- /dev/null +++ b/src/content/blog/Dockerizing apps.txt @@ -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 diff --git a/src/content/blog/Setup.txt b/src/content/blog/Setup.txt new file mode 100644 index 0000000..85eb3bb --- /dev/null +++ b/src/content/blog/Setup.txt @@ -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. \ No newline at end of file diff --git a/src/content/blog/aws iam.txt b/src/content/blog/aws iam.txt new file mode 100644 index 0000000..8caba07 --- /dev/null +++ b/src/content/blog/aws iam.txt @@ -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. \ No newline at end of file diff --git a/src/content/blog/ec2 & rds.txt b/src/content/blog/ec2 & rds.txt new file mode 100644 index 0000000..d72f145 --- /dev/null +++ b/src/content/blog/ec2 & rds.txt @@ -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 \ No newline at end of file diff --git a/src/content/blog/first-post.md b/src/content/blog/first-post.md index c8d07a2..6720740 100644 --- a/src/content/blog/first-post.md +++ b/src/content/blog/first-post.md @@ -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``` diff --git a/src/content/blog/monitoring.txt b/src/content/blog/monitoring.txt new file mode 100644 index 0000000..abf90e1 --- /dev/null +++ b/src/content/blog/monitoring.txt @@ -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. \ No newline at end of file diff --git a/src/content/blog/postwa.md b/src/content/blog/postwa.md index f9e4fa3..8ccdedc 100644 --- a/src/content/blog/postwa.md +++ b/src/content/blog/postwa.md @@ -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 \ No newline at end of file +## Svelte diff --git a/src/content/blog/project description.txt b/src/content/blog/project description.txt new file mode 100644 index 0000000..196e765 --- /dev/null +++ b/src/content/blog/project description.txt @@ -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) \ No newline at end of file diff --git a/src/content/blog/second-post.md b/src/content/blog/second-post.md index 930f194..f985f1e 100644 --- a/src/content/blog/second-post.md +++ b/src/content/blog/second-post.md @@ -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. \ No newline at end of file +--- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore. diff --git a/src/content/blog/skillset.txt b/src/content/blog/skillset.txt new file mode 100644 index 0000000..397823d --- /dev/null +++ b/src/content/blog/skillset.txt @@ -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: \ No newline at end of file diff --git a/src/content/blog/turbo and docker.txt b/src/content/blog/turbo and docker.txt new file mode 100644 index 0000000..db09887 --- /dev/null +++ b/src/content/blog/turbo and docker.txt @@ -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 \ No newline at end of file diff --git a/src/lib/assets/code.css b/src/lib/assets/code.css new file mode 100644 index 0000000..8492174 --- /dev/null +++ b/src/lib/assets/code.css @@ -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; +} diff --git a/src/lib/assets/global.css b/src/lib/assets/global.css new file mode 100644 index 0000000..62cd6b8 --- /dev/null +++ b/src/lib/assets/global.css @@ -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; +} diff --git a/src/lib/cloudinary.ts b/src/lib/cloudinary.ts new file mode 100644 index 0000000..001381a --- /dev/null +++ b/src/lib/cloudinary.ts @@ -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; diff --git a/src/lib/components/home/QuickCards.svelte b/src/lib/components/home/QuickCards.svelte index b8ee942..d59a98c 100644 --- a/src/lib/components/home/QuickCards.svelte +++ b/src/lib/components/home/QuickCards.svelte @@ -1,26 +1,26 @@
-
- -

Development

-

- I possess a wide range of skills that enable me to develop visually appealing and - interactive user interfaces for web applications. -

-
-
- -

Design

-

- Over the years, I have honed my ability to create visually appealing interfaces that - are both user-friendly and intuitive. -

-
-
- -

User Experience

-

- I understand the importance of creating a seamless UX for end-users. Which includes - a solid understanding user behavior. -

-
+
+ +

Development

+

+ I hone my problem solving and coding skills all the time. I try to learn the underlying + mechanics and use abstractions where relevant. +

+
+
+ +

Creativity & Presentation

+

+ I try to create better, novel ways to approach problems and give my best to present them + in a clear and concise manner. +

+
+
+ +

Teamwork & Stakeholders

+

+ From my experience with a wide range of roles I understand the how to communication with + stakeholders across an organization. +

+
diff --git a/src/lib/utils/file.js b/src/lib/utils/file.js new file mode 100644 index 0000000..232deb0 --- /dev/null +++ b/src/lib/utils/file.js @@ -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); + } + }); +} diff --git a/src/lib/utils/shiki-highlighter.js b/src/lib/utils/shiki-highlighter.js new file mode 100644 index 0000000..344e1d1 --- /dev/null +++ b/src/lib/utils/shiki-highlighter.js @@ -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 ? `${options.lang}` : ''; + return `
${lang}${lines.map(lineRenderer(options)).join('\n')}\n
`; +} + +const lineRenderer = (options) => (line) => { + const output = line.map(tokenRenderer(options)).join(''); + const { leadingWS, content, trailingWS } = splitLeadingAndTrailingWS(output); + return `${leadingWS || ''}${content || ''}${ + 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}${escape(content)}${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 } + ) + ); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 58026c4..27226db 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,48 +1,26 @@ Matt Morin - - + + - - + + -
- - +
- + - +
diff --git a/static/android-chrome-192x192.png b/static/android-chrome-192x192.png new file mode 100644 index 0000000..5abe545 Binary files /dev/null and b/static/android-chrome-192x192.png differ diff --git a/static/android-chrome-512x512.png b/static/android-chrome-512x512.png new file mode 100644 index 0000000..f367ad4 Binary files /dev/null and b/static/android-chrome-512x512.png differ diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png new file mode 100644 index 0000000..efd842f Binary files /dev/null and b/static/apple-touch-icon.png differ diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png new file mode 100644 index 0000000..a5a977e Binary files /dev/null and b/static/favicon-16x16.png differ diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png new file mode 100644 index 0000000..450cb63 Binary files /dev/null and b/static/favicon-32x32.png differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..940cfde Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/site.webmanifest b/static/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/static/site.webmanifest @@ -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"} \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index a776fd4..22889f8 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -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; diff --git a/vite.config.ts b/vite.config.ts index 36be9c6..82d7044 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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())