Compare commits
148 Commits
1eab47ee41
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
1070662164 | |||
9a36a46ad1
|
|||
558bca7f56
|
|||
bc7099d627
|
|||
f71b054ae5
|
|||
0e09633e83 | |||
a83022c46e
|
|||
b1944e64d9
|
|||
188f4616ea
|
|||
de33ca3814 | |||
e9143bfdf4
|
|||
87ff7e5dbd
|
|||
8aea5bc94f | |||
dc00eff17c | |||
fe36594189
|
|||
aa8e55c9a6 | |||
e2b276dc0e
|
|||
6721dc5eef | |||
6c5d16ef7a | |||
03b95a6c8c | |||
962e3614e3 | |||
63c84e1430 | |||
3b2a8a6611 | |||
9028175ae4
|
|||
280c8e15ad
|
|||
e081a0cb3e
|
|||
41c6964679 | |||
f25c6ddd68 | |||
9b49710556
|
|||
a0c3b27aab | |||
005dfb6929
|
|||
b96c6d2caf | |||
ef37e45281 | |||
b55538345f
|
|||
b586385d6d | |||
2d3046da48 | |||
67f9844534
|
|||
50b8845e6c | |||
79f6e8e90b | |||
25f3db52ec
|
|||
a46ac458dc
|
|||
206c5665a2
|
|||
fd3c620cb9
|
|||
c52d185f76
|
|||
538d9593c2
|
|||
24a7ebf02a
|
|||
fc642a4ecd
|
|||
d9e8b4b56c
|
|||
bd689bdb44 | |||
051cd42fdb
|
|||
da5f47a841
|
|||
5fe7b83c47 | |||
931e4d2abe | |||
33ddd2add0 | |||
8cd763b9d0 | |||
ce38e88885 | |||
e1160b3462 | |||
7042b2d500 | |||
5f1a1d4959 | |||
da2f2bc380 | |||
c3055a6882 | |||
3557a6a6ad | |||
f34761d094 | |||
f7e3acf384 | |||
ccbaa41cab | |||
f33456bae3 | |||
4cab417bdd | |||
cb1304aaeb | |||
27488fe860 | |||
b9c4ec540a | |||
c3f0be36a3 | |||
71dc20c0ca | |||
9da13b76d3 | |||
712d7857db | |||
8ab101727e | |||
5fb67af755 | |||
15f30c09b1 | |||
e5c4243a1f | |||
47a43f2b0e | |||
1a6c5194e5 | |||
aaab8f2c98 | |||
2170344c9b | |||
45208d0ff9 | |||
099bf34c19 | |||
93ca58f487 | |||
1087f95bc4 | |||
94b1e80358 | |||
753e091a85 | |||
b5f6b3adf8 | |||
7cefc86f08 | |||
dfd47c0d98 | |||
e4da310ef2 | |||
196e338e66 | |||
8ccc8e2129 | |||
83361c4199 | |||
112e0f1a5c | |||
e843870afd | |||
74dc1196cd | |||
306396cce1 | |||
a2acc03f7c | |||
6f0f5f1cf7 | |||
278fc640ce | |||
2e07de0e71 | |||
e0ea53a9d3 | |||
632eed6810 | |||
dd4dfdea78 | |||
377f87ced3 | |||
d8d40ddc30 | |||
ee2098e6e6 | |||
d2066087ae | |||
d187ec70c3 | |||
4a734b66f9 | |||
cbdc81c4ce | |||
7bd03be127 | |||
8d5319ac4a | |||
26357e531d | |||
eac1534497 | |||
94de27084c | |||
0a6ede8125 | |||
ebf9a21478 | |||
86652a4f09 | |||
3f171dea3c | |||
6b0af2fb3e | |||
1b33bd398d | |||
f31f180687 | |||
8b9a2ac8d5 | |||
212103ab71 | |||
7d7012eec6 | |||
b2b56480b8 | |||
79795bc060 | |||
6961d9c34d | |||
0797fd9eff | |||
d69078ff26 | |||
ba437de706 | |||
065a6a2d17 | |||
3eb3b6845b | |||
71227d7d78 | |||
52c1abb0b7 | |||
de234ddf8c | |||
3769f3edc3 | |||
87b2e808f7 | |||
189ca5034b | |||
71405fd5a2 | |||
357f0b06f3 | |||
30a5d53bc0 | |||
bd183bf832 | |||
69cfbf1b44 | |||
a75c919929 |
18
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "luke-else.co.uk",
|
||||
"image": "git.luke-else.co.uk/luke-else/lab:latest",
|
||||
"remoteUser": "dev",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "zsh"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-vscode-remote.remote-containers",
|
||||
"svelte.svelte-vscode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "pnpm install"
|
||||
}
|
@@ -1,18 +1,19 @@
|
||||
# .dockerignore
|
||||
|
||||
.vscode
|
||||
|
||||
node_modules
|
||||
|
||||
.git
|
||||
.gitattributes
|
||||
|
||||
.eslintignore
|
||||
.eslintrc.cjs
|
||||
|
||||
.prettierrc
|
||||
.pretieriignore
|
||||
|
||||
README.md
|
||||
|
||||
# .dockerignore
|
||||
|
||||
.vscode
|
||||
|
||||
node_modules
|
||||
|
||||
.git
|
||||
.gitattributes
|
||||
.gitea
|
||||
|
||||
.eslintignore
|
||||
.eslintrc.cjs
|
||||
|
||||
.prettierrc
|
||||
.pretieriignore
|
||||
|
||||
README.md
|
||||
|
||||
Dockerfile
|
30
.gitea/workflows/dev.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Build and Push Development Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ development ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.CONTAINER_REGISTRY }}
|
||||
username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.CONTAINER_REGISTRY_PASSKEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and Tag Docker Image
|
||||
run: |
|
||||
docker build -t ${{ secrets.CONTAINER_REGISTRY }}/${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:dev .
|
||||
|
||||
- name: Push Docker Image
|
||||
run: |
|
||||
docker push ${{ secrets.CONTAINER_REGISTRY }}/${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:dev
|
@@ -1,31 +1,30 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.CONTAINER_REGISTRY_PASSKEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and Tag Docker Image
|
||||
run: |
|
||||
docker build -t ${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:latest .
|
||||
|
||||
- name: Push Docker Image
|
||||
run: |
|
||||
docker push ${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:latest
|
||||
name: Build and Push Latest Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.CONTAINER_REGISTRY }}
|
||||
username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.CONTAINER_REGISTRY_PASSKEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and Tag Docker Image
|
||||
run: |
|
||||
docker build -t ${{ secrets.CONTAINER_REGISTRY }}/${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:latest .
|
||||
|
||||
- name: Push Docker Image
|
||||
run: |
|
||||
docker push ${{ secrets.CONTAINER_REGISTRY }}/${{ secrets.CONTAINER_REGISTRY_USERNAME }}/luke-else.co.uk:latest
|
23
.gitignore
vendored
@@ -1,11 +1,12 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vscode
|
||||
.DS_Store
|
||||
node_modules
|
||||
.pnpm-store
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vscode
|
||||
|
6
.npmrc
@@ -1,2 +1,4 @@
|
||||
engine-strict=true
|
||||
resolution-mode=highest
|
||||
engine-strict=true
|
||||
resolution-mode=highest
|
||||
|
||||
@luke-else:registry=https://git.luke-else.co.uk/api/packages/luke-else/npm/
|
||||
|
@@ -1,13 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
19
.prettierrc
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
94
README.md
@@ -1,38 +1,56 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
## Welcome
|
||||
|
||||
This site contains information relating to my personal situation, however, you are able to clone this project and change the `me.json` file to update this as required.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/images/main.png" width="40%">
|
||||
<img src="assets/images/light_mode.png" width="40%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/images/skills.png" width="30%">
|
||||
<img src="assets/images/repos.png" width="30%">
|
||||
<img src="assets/images/contact.png" width="30%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/images/experience.png" width="30%">
|
||||
</p>
|
||||
|
||||
## Getting Started
|
||||
|
||||
Get starting but installing all of the dependencies of the project.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
|
||||
```
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of the app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
|
||||
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. In this case, vite is used.
|
||||
|
BIN
assets/images/contact.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
assets/images/experience.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
assets/images/light_mode.png
Normal file
After Width: | Height: | Size: 181 KiB |
BIN
assets/images/main.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
assets/images/repos.png
Normal file
After Width: | Height: | Size: 298 KiB |
BIN
assets/images/skills.png
Normal file
After Width: | Height: | Size: 85 KiB |
41
dockerfile
@@ -1,20 +1,21 @@
|
||||
FROM node:lts-slim as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN rm -rf node_modules
|
||||
RUN rm -rf build
|
||||
COPY . .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM node:lts-slim as run
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/package.json ./package.json
|
||||
COPY --from=build /app/build ./build
|
||||
RUN npm install --omit=dev
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT [ "npm", "run", "start" ]
|
||||
FROM git.luke-else.co.uk/luke-else/nodejs:latest AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN rm -rf node_modules
|
||||
RUN rm -rf build
|
||||
COPY . .
|
||||
RUN pnpm install
|
||||
RUN pnpm run build
|
||||
|
||||
FROM git.luke-else.co.uk/luke-else/nodejs:latest AS run
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/package.json ./package.json
|
||||
COPY --from=build /app/.npmrc ./.npmrc
|
||||
COPY --from=build /app/build ./build
|
||||
RUN pnpm install --prod
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT [ "pnpm", "run", "start" ]
|
||||
|
2088
package-lock.json
generated
31
package.json
@@ -13,17 +13,24 @@
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.0.5",
|
||||
"svelte-check": "^3.4.3",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.4.2"
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@sveltejs/adapter-auto": "6.0.0",
|
||||
"@sveltejs/adapter-node": "5.2.12",
|
||||
"@sveltejs/kit": "2.20.8",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-svelte": "3.3.3",
|
||||
"svelte": "5.28.2",
|
||||
"svelte-check": "4.1.7",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "6.3.5"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@luke-else/component-lib": "^1.1.5",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"svelte-toasts": "^1.1.2",
|
||||
"tailwindcss": "^4.1.13"
|
||||
}
|
||||
}
|
||||
|
1934
pnpm-lock.yaml
generated
Normal file
1
src/app.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
24
src/app.d.ts
vendored
@@ -1,12 +1,12 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
160
src/app.html
@@ -1,129 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="Luke Else - Software Developer at Thales UK. I specialise in developing distributed systems in C++ using highly scalable internal frameworks. I also develop backend and system applications in my spare time using both Svelte, Rust and C++. Feel free to check my work out at https://git.luke-else.co.uk." />
|
||||
<meta name="author" content="Luke Else (mail@luke-else.co.uk)" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@v2.15.1/devicon.min.css">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font: Consolas, 'Cascadia Code', Monaco, 'SF Mono', 'DejaVu Sans Mono', 'Roboto Mono';
|
||||
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font);
|
||||
font-size: 110%;
|
||||
margin: 2rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: var(--header);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: .2em solid var(--bg-grad-3);
|
||||
border-radius: 5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar,
|
||||
*::-webkit-scrollbar-thumb {
|
||||
width: 26px;
|
||||
border-radius: 13px;
|
||||
background-clip: padding-box;
|
||||
border: 10px solid transparent;
|
||||
color: var(--fg);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover{
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
box-shadow: inset 0 0 0 10px;
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.not-required {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
color: var(--link);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
border-bottom: 2px solid var(--fg);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
a:hover:after {
|
||||
width: 100%;
|
||||
color: var(--glow);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--glow);
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: var(--header);
|
||||
}
|
||||
|
||||
.button {
|
||||
color: var(--fg);
|
||||
background-color: var(--bg-grad-1);
|
||||
box-shadow: .1em .1em .1em var(--link);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
box-shadow: .3em .3em .3em var(--header);
|
||||
}
|
||||
|
||||
@keyframes animationName {
|
||||
0% { opacity:0; }
|
||||
50% { opacity:1; }
|
||||
100% { opacity:0; }
|
||||
}
|
||||
@-o-keyframes animationName{
|
||||
0% { opacity:0; }
|
||||
50% { opacity:1; }
|
||||
100% { opacity:0; }
|
||||
}
|
||||
@-moz-keyframes animationName{
|
||||
0% { opacity:0; }
|
||||
50% { opacity:1; }
|
||||
100% { opacity:0; }
|
||||
}
|
||||
@-webkit-keyframes animationName{
|
||||
0% { opacity:0; }
|
||||
50% { opacity:1; }
|
||||
100% { opacity:0; }
|
||||
}
|
||||
.elementToFadeInAndOut {
|
||||
-webkit-animation: animationName 1.5s infinite;
|
||||
-moz-animation: animationName 1.5s infinite;
|
||||
-o-animation: animationName 1.5s infinite;
|
||||
animation: animationName 1.5s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Luke Else - Software Developer at Thales UK. I specialise in developing distributed systems in C++ using highly scalable internal frameworks. I also develop backend and system applications in my spare time using both Svelte, Rust and C++. Feel free to check my work out at https://git.luke-else.co.uk."
|
||||
/>
|
||||
<meta name="author" content="Luke Else (mail@luke-else.co.uk)" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/devicons/devicon@v2.15.1/devicon.min.css"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<script
|
||||
async
|
||||
src="https://tracking.luke-else.co.uk/tracker.js"
|
||||
data-ackee-server="https://tracking.luke-else.co.uk"
|
||||
data-ackee-domain-id="6c59ab88-dc6d-4d53-9831-0d6bff919dcd"
|
||||
data-ackee-opts='{ "detailed": true }'
|
||||
></script>
|
||||
%sveltekit.head%
|
||||
|
||||
<style></style>
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
67
src/lib/api/git.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { GitRepo } from "../types";
|
||||
|
||||
const API_BASE_URL = "https://git.luke-else.co.uk/api/v1";
|
||||
export const IMAGE_URL_SUFFIX = "/raw/branch/main/assets/images/main.png";
|
||||
|
||||
|
||||
export async function fetchRepos(): Promise<GitRepo[]> {
|
||||
try {
|
||||
console.log("Fetching repos...");
|
||||
const response = await fetch(`${API_BASE_URL}/repos/search?sort=updated&order=desc&limit=12`, {
|
||||
headers: {
|
||||
// "Authorization": `token ${ACCESS_TOKEN}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data: { data: GitRepo[] } = await response.json();
|
||||
return data.data; // Extract the list of repositories
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch repos:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function timeSince(inputDate: Date | string): string {
|
||||
const date = new Date(inputDate); // Ensure it's a Date object
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error("Invalid date provided");
|
||||
}
|
||||
|
||||
const now: Date = new Date();
|
||||
const diffInMs: number = now.getTime() - date.getTime();
|
||||
const diffInSeconds: number = Math.floor(diffInMs / 1000);
|
||||
const diffInMinutes: number = Math.floor(diffInSeconds / 60);
|
||||
const diffInHours: number = Math.floor(diffInMinutes / 60);
|
||||
const diffInDays: number = Math.floor(diffInHours / 24);
|
||||
const diffInMonths: number = Math.floor(diffInDays / 30); // Approximate
|
||||
const diffInYears: number = Math.floor(diffInDays / 365); // Approximate
|
||||
|
||||
if (diffInDays === 0) return "Today";
|
||||
if (diffInDays === 1) return "Yesterday";
|
||||
if (diffInDays < 7) return `${diffInDays} days ago`;
|
||||
if (diffInDays < 30) return `${Math.floor(diffInDays / 7)} week${diffInDays >= 14 ? 's' : ''} ago`;
|
||||
if (diffInMonths < 12) return `${diffInMonths} month${diffInMonths > 1 ? 's' : ''} ago`;
|
||||
return `${diffInYears} year${diffInYears > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
|
||||
export async function checkImage(repo: GitRepo): Promise<boolean> {
|
||||
try {
|
||||
const URL = repo.html_url + IMAGE_URL_SUFFIX;
|
||||
console.log("Checking image:", URL);
|
||||
const response = await fetch(URL);
|
||||
if (response.ok) {
|
||||
console.log("Image found!");
|
||||
return true;
|
||||
} else {
|
||||
console.log("Image not found!");
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function onClick() {
|
||||
dispatch('click');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
flex: 2 1 15em;
|
||||
padding: .5em 2.5em 2em 2.5em;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: .5em;
|
||||
scroll-snap-align: start;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.card:hover {
|
||||
box-shadow: .5em .5em .5em var(--hover);
|
||||
}
|
||||
|
||||
.card .card-header :global(div) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card .card-content :global(div) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.card .card-footer :global(div){
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
max-width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="card" on:click={onClick}>
|
||||
<div class="card-header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="card-content">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<hr class="not-required"/>
|
||||
<div class="card-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let showModal: boolean;
|
||||
|
||||
let dialog: HTMLDialogElement;
|
||||
|
||||
$: if (dialog && showModal) dialog.showModal();
|
||||
|
||||
import CloseIcon from "./Toasts/CloseIcon.svelte";
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
|
||||
<dialog
|
||||
bind:this={dialog}
|
||||
on:close={() => (showModal = false)}
|
||||
on:click|self={() => dialog.close()}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div on:click|stopPropagation>
|
||||
<slot name="header" />
|
||||
<hr />
|
||||
<slot />
|
||||
<hr />
|
||||
</div>
|
||||
<button class="close" on:click={() => dialog.close()}>
|
||||
<CloseIcon width="0.8em" />
|
||||
</button>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
max-width: 70%;
|
||||
border-radius: 0.2em;
|
||||
border: none;
|
||||
padding: 0em 2em 2em 2em;
|
||||
border-left: 2em;
|
||||
border-right: 2em;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
box-shadow: .5em .5em .5em var(--header);
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
dialog > div {
|
||||
padding: 1em;
|
||||
}
|
||||
dialog[open] {
|
||||
animation: zoom 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
@keyframes zoom {
|
||||
from {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
dialog[open]::backdrop {
|
||||
animation: fade 0.3s ease-out;
|
||||
}
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1.5em;
|
||||
color: var(--fg);
|
||||
background: transparent;
|
||||
border: 0 none;
|
||||
padding: 0;
|
||||
margin: 0 0 0 auto;
|
||||
line-height: 1;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
@@ -1,102 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let darkMode: boolean = true;
|
||||
|
||||
function onThemeSwitch() {
|
||||
darkMode = !darkMode;
|
||||
|
||||
localStorage.setItem('theme', darkMode ? 'dark' : 'light');
|
||||
|
||||
darkMode
|
||||
? document.documentElement.classList.add('dark')
|
||||
: document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
|
||||
if (browser) {
|
||||
if (
|
||||
localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark');
|
||||
darkMode = true;
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
darkMode = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: absolute;
|
||||
top: 0em;
|
||||
right: 0em;
|
||||
display: inline-block;
|
||||
width: 3.75em;
|
||||
height: 2.125em;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background-color: var(--bg-grad-4);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 80%;
|
||||
width: 45%;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
input:checked + .slider {
|
||||
background-color: var(--bg-grad-1);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(1.625em);
|
||||
-ms-transform: translateX(1.625em);
|
||||
transform: translateX(1.625em);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 2.125em;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href={`/themes/${darkMode ? 'dark' : 'light'}.css`} />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
<div class="toggle-wrapper not-required">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked={darkMode} on:click={onThemeSwitch}>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
@@ -1,18 +0,0 @@
|
||||
<script>
|
||||
export let width = "1em"
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={width}
|
||||
style="text-align: center; display: inline-block;"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 352 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
|
||||
/>
|
||||
</svg>
|
@@ -1,19 +0,0 @@
|
||||
<script>
|
||||
export let width = "1em"
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={width}
|
||||
style="text-align: center; display: inline-block;"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M256 40c118.621 0 216 96.075 216 216 0 119.291-96.61 216-216 216-119.244 0-216-96.562-216-216 0-119.203 96.602-216 216-216m0-32C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm-11.49 120h22.979c6.823 0 12.274 5.682 11.99 12.5l-7 168c-.268 6.428-5.556 11.5-11.99 11.5h-8.979c-6.433 0-11.722-5.073-11.99-11.5l-7-168c-.283-6.818 5.167-12.5 11.99-12.5zM256 340c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28z"
|
||||
class=""
|
||||
></path>
|
||||
</svg>
|
@@ -1,18 +0,0 @@
|
||||
<script>
|
||||
export let width = "1em"
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={width}
|
||||
style="text-align: center; display: inline-block;"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M256 40c118.621 0 216 96.075 216 216 0 119.291-96.61 216-216 216-119.244 0-216-96.562-216-216 0-119.203 96.602-216 216-216m0-32C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm-36 344h12V232h-12c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12h48c6.627 0 12 5.373 12 12v140h12c6.627 0 12 5.373 12 12v8c0 6.627-5.373 12-12 12h-72c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12zm36-240c-17.673 0-32 14.327-32 32s14.327 32 32 32 32-14.327 32-32-14.327-32-32-32z"
|
||||
/>
|
||||
</svg>
|
@@ -1,18 +0,0 @@
|
||||
<script>
|
||||
export let width = "1em"
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={width}
|
||||
style="text-align: center; display: inline-block;"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 464c-118.664 0-216-96.055-216-216 0-118.663 96.055-216 216-216 118.664 0 216 96.055 216 216 0 118.663-96.055 216-216 216zm141.63-274.961L217.15 376.071c-4.705 4.667-12.303 4.637-16.97-.068l-85.878-86.572c-4.667-4.705-4.637-12.303.068-16.97l8.52-8.451c4.705-4.667 12.303-4.637 16.97.068l68.976 69.533 163.441-162.13c4.705-4.667 12.303-4.637 16.97.068l8.451 8.52c4.668 4.705 4.637 12.303-.068 16.97z"
|
||||
/>
|
||||
</svg>
|
@@ -1,67 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import { Toast, ToastType } from "$lib/toast";
|
||||
import SuccessIcon from "./SuccessIcon.svelte";
|
||||
import ErrorIcon from "./ErrorIcon.svelte";
|
||||
import InfoIcon from "./InfoIcon.svelte";
|
||||
import CloseIcon from "./CloseIcon.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let toastData: Toast;
|
||||
</script>
|
||||
|
||||
<article class={toastData.type.toString().toLowerCase()} role="alert" transition:fade>
|
||||
{#if toastData.type === ToastType.Success}
|
||||
<SuccessIcon width="1.1em" />
|
||||
{:else if toastData.type === ToastType.Error}
|
||||
<ErrorIcon width="1.1em" />
|
||||
{:else}
|
||||
<InfoIcon width="1.1em" />
|
||||
{/if}
|
||||
|
||||
<div class="text">
|
||||
{toastData.text}
|
||||
</div>
|
||||
|
||||
{#if toastData.dismissable}
|
||||
<button class="close" on:click={() => dispatch("dismiss")}>
|
||||
<CloseIcon width="0.8em" />
|
||||
</button>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<style lang="postcss">
|
||||
article {
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 auto 0.5rem auto;
|
||||
width: 20rem;
|
||||
max-width: 80%;
|
||||
}
|
||||
.error {
|
||||
background: IndianRed;
|
||||
}
|
||||
.success {
|
||||
background: MediumSeaGreen;
|
||||
}
|
||||
.info {
|
||||
background: SkyBlue;
|
||||
}
|
||||
.text {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.close {
|
||||
color: white;
|
||||
background: transparent;
|
||||
border: 0 none;
|
||||
padding: 0;
|
||||
margin: 0 0 0 auto;
|
||||
line-height: 1;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
@@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Toast from "./Toast.svelte";
|
||||
|
||||
import { dismissToast, toasts } from "$lib/store";
|
||||
</script>
|
||||
|
||||
{#if $toasts}
|
||||
<section>
|
||||
{#each $toasts as toast (toast.id)}
|
||||
<Toast toastData = {toast} on:dismiss={() => dismissToast(toast.id)} />
|
||||
{/each}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte';
|
||||
import type { TimelinePosition, TimelineConfig } from '../types';
|
||||
export let position: TimelinePosition = 'right';
|
||||
export let style: string = null;
|
||||
|
||||
setContext<TimelineConfig>('TimelineConfig', { rootPosition: position });
|
||||
</script>
|
||||
|
||||
<ul class="timeline" {style}>
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 16px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
@@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let style: string = null;
|
||||
</script>
|
||||
|
||||
<span class="timeline-connector" {style} />
|
||||
|
||||
<style>
|
||||
.timeline-connector {
|
||||
width: 2px;
|
||||
background-color: #bdbdbd;
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
@@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import type { TimelineConfig, TimelinePosition } from '../types';
|
||||
export let style: string = null;
|
||||
|
||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
||||
|
||||
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
||||
</script>
|
||||
|
||||
<div class={`timeline-content ${itemPosition}`} {style}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline-content {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
margin: 6px 16px;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let style: string = null;
|
||||
</script>
|
||||
|
||||
<span class="timeline-dot" {style}>
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.timeline-dot {
|
||||
background-color: #121212;
|
||||
border: solid 2px #121212;
|
||||
display: flex;
|
||||
align-self: baseline;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
margin: 11.5px 0;
|
||||
}
|
||||
</style>
|
@@ -1,58 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import type { TimelinePosition, ParentPosition, TimelineConfig } from '../types';
|
||||
|
||||
export let position: ParentPosition | null = null;
|
||||
export let style: string = null;
|
||||
|
||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||
const itemPosition = position ? position : config.rootPosition;
|
||||
setContext<TimelinePosition>('ParentPosition', itemPosition);
|
||||
</script>
|
||||
|
||||
<li class={`timeline-item ${itemPosition}`} {style}>
|
||||
{#if !$$slots['opposite-content']}
|
||||
<div class="opposite-block" />
|
||||
{:else}
|
||||
<slot name="opposite-content" />
|
||||
{/if}
|
||||
<slot />
|
||||
</li>
|
||||
|
||||
<style>
|
||||
:global(.alternate:nth-of-type(even) > .timeline-content) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:global(.alternate:nth-of-type(odd) > .timeline-opposite-content) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.opposite-block {
|
||||
flex: 1;
|
||||
margin: 6px 16px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.alternate:nth-of-type(even) {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.alternate:nth-of-type(odd) {
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
@@ -1,31 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import type { TimelineConfig, TimelinePosition } from '../types';
|
||||
export let style: string = null;
|
||||
|
||||
const config = getContext<TimelineConfig>('TimelineConfig');
|
||||
const parentPosition = getContext<TimelinePosition>('ParentPosition');
|
||||
|
||||
const itemPosition = parentPosition ? parentPosition : config.rootPosition;
|
||||
</script>
|
||||
|
||||
<div class={`timeline-opposite-content ${itemPosition}`} {style}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline-opposite-content {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
margin-right: auto;
|
||||
margin: 6px 16px;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let style: string = null;
|
||||
</script>
|
||||
|
||||
<div class="timeline-separator" {style}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline-separator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@@ -1,5 +1,5 @@
|
||||
export async function getJson(path: string) {
|
||||
let response = await fetch(path);
|
||||
let users = await response.json();
|
||||
return users;
|
||||
export async function getJson(path: string) {
|
||||
let response = await fetch(path);
|
||||
let users = await response.json();
|
||||
return users;
|
||||
}
|
@@ -1,39 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
||||
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
||||
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
||||
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
||||
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
||||
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
||||
|
||||
import Toasts from '$lib/components/Toasts/Toasts.svelte';
|
||||
import Toast from '$lib/components/Toasts/Toast.svelte';
|
||||
import CloseIcon from '$lib/components/Toasts/CloseIcon.svelte';
|
||||
import InfoIcon from '$lib/components/Toasts/InfoIcon.svelte';
|
||||
import SuccessIcon from '$lib/components/Toasts/SuccessIcon.svelte';
|
||||
import ErrorIcon from '$lib/components/Toasts/ErrorIcon.svelte';
|
||||
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
|
||||
|
||||
export {
|
||||
Timeline,
|
||||
TimelineItem,
|
||||
TimelineSeparator,
|
||||
TimelineDot,
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineOppositeContent,
|
||||
|
||||
Toasts,
|
||||
Toast,
|
||||
CloseIcon,
|
||||
InfoIcon,
|
||||
SuccessIcon,
|
||||
ErrorIcon,
|
||||
|
||||
Card,
|
||||
Modal
|
||||
};
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
@@ -1,28 +0,0 @@
|
||||
import { ToastType, type Toast } from "$lib/toast";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const toasts: Writable<Toast[]> = writable([]);
|
||||
|
||||
export const addToast = (toast: Toast) => {
|
||||
// Create a unique ID so we can easily find/remove it
|
||||
// if it is dismissible/has a timeout.
|
||||
toast.id = Math.floor(Math.random() * 10000);
|
||||
|
||||
// Setup some sensible defaults for a toast.
|
||||
const defaults = {
|
||||
id: toast.id,
|
||||
type: ToastType.Info,
|
||||
dismissible: true,
|
||||
timeout: 3000,
|
||||
};
|
||||
|
||||
// Push the toast to the top of the list of toasts
|
||||
toasts.update((all) => [{ ...defaults, ...toast }, ...all]);
|
||||
|
||||
// If toast is dismissible, dismiss it after "timeout" amount of time.
|
||||
if (toast.timeout) setTimeout(() => dismissToast(toast.id), toast.timeout);
|
||||
};
|
||||
|
||||
export const dismissToast = (id: number) => {
|
||||
toasts.update((all) => all.filter((t) => t.id !== id));
|
||||
};
|
12
src/lib/stores.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { writable } from "svelte/store";
|
||||
import type { GitRepo } from "./types";
|
||||
import { fetchRepos } from "./api/git";
|
||||
|
||||
////////////////////////////////////////
|
||||
// Git Repo Stores
|
||||
////////////////////////////////////////
|
||||
export const repos = writable<GitRepo[]>([]);
|
||||
|
||||
export async function loadRepos() {
|
||||
repos.set(await fetchRepos());
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* @enum Used to refer to the type of toast being displayed
|
||||
*/
|
||||
export enum ToastType {
|
||||
Info = "info",
|
||||
Success = "success",
|
||||
Error = "error"
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Toast Notification
|
||||
*/
|
||||
export class Toast {
|
||||
constructor(text: String, type: ToastType, dismissable: boolean, timeout: number ) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.dismissable = dismissable;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
id: number = 0;
|
||||
text: String;
|
||||
type: ToastType;
|
||||
dismissable: Boolean;
|
||||
timeout: number;
|
||||
}
|
9
src/lib/types.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
type TimelinePosition = 'right' | 'left' | 'alternate';
|
||||
|
||||
type ParentPosition = 'right' | 'left';
|
||||
|
||||
type TimelineConfig = {
|
||||
rootPosition: TimelinePosition;
|
||||
};
|
||||
|
||||
export { TimelinePosition, ParentPosition, TimelineConfig };
|
22
src/lib/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export type TimelinePosition = 'right' | 'left' | 'alternate';
|
||||
|
||||
export type ParentPosition = 'right' | 'left';
|
||||
|
||||
export type TimelineConfig = {
|
||||
rootPosition: TimelinePosition;
|
||||
};
|
||||
|
||||
export interface GitRepo {
|
||||
name: string;
|
||||
description: string;
|
||||
language: string;
|
||||
size: number;
|
||||
updated_at: Date;
|
||||
html_url: string;
|
||||
private: boolean;
|
||||
fork: boolean;
|
||||
owner: {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
}
|
211
src/main.svelte
@@ -1,125 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { getJson } from "$lib/data";
|
||||
import { Toast, ToastType } from "$lib/toast";
|
||||
import { addToast } from "$lib/store";
|
||||
|
||||
import Skills from './skills.svelte';
|
||||
|
||||
import Timeline from "./timeline.svelte";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main-card {
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 1em;
|
||||
padding: .2em 2em 2em 2em;
|
||||
box-shadow: .5em .5em .5em var(--glow);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile {
|
||||
border-radius: 100%;
|
||||
height: 8em;
|
||||
width: 8em;
|
||||
padding: 1em 1em 1em 1em;
|
||||
border: .5em solid var(--bg-grad-3);
|
||||
}
|
||||
|
||||
.about {
|
||||
padding: 0em 5% 0em 5%;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.flex-container {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.about {
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.card-header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
/* Skills Cards CSS */
|
||||
.container {
|
||||
padding-top: 3em;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 3em 3em;
|
||||
padding: 2em 0em 2em 0em;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{#await getJson('/json/me.json')}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
{:then info}
|
||||
<div class="main-card">
|
||||
<div class="card-header">
|
||||
<h1>{info.name}</h1>
|
||||
<h3 class="not-required">{info.job_title}</h3>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex-container">
|
||||
<img class="profile not-required" src={info.profile_photo} alt="{info.name}'s Profile Photo">
|
||||
<p class="about">{@html info.about}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1>Skills</h1>
|
||||
<hr />
|
||||
<div class="cards">
|
||||
<Skills skills="{info.skills}"></Skills>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1>Experience</h1>
|
||||
<hr />
|
||||
<!-- https://github.com/K-Sato1995/svelte-vertical-timeline -->
|
||||
<Timeline timelineData="{info.timeline}"></Timeline>
|
||||
</div>
|
||||
|
||||
<div style="display: none;">{addToast(new Toast("Click on a skill to open a prompt", ToastType.Info, true, 8_000))}</div>
|
||||
<div style="display: none;">{addToast(new Toast("Welcome!", ToastType.Success, true, 7_000))}</div>
|
||||
{:catch}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1>Unable to load portfolio overview data</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: none;">{addToast(new Toast("Unable to load me.json", ToastType.Error, true, 3000))}</div>
|
||||
{/await}
|
||||
<script lang="ts">
|
||||
import { getJson } from '$lib/data';
|
||||
import { toasts } from 'svelte-toasts';
|
||||
|
||||
import {
|
||||
Loading,
|
||||
Section,
|
||||
Card,
|
||||
GridGallery,
|
||||
SkillProgress,
|
||||
Timeline,
|
||||
Collapsible
|
||||
} from '@luke-else/component-lib';
|
||||
</script>
|
||||
|
||||
{#await getJson('/json/me.json')}
|
||||
<Loading />
|
||||
{:then info}
|
||||
<div style="display: none;">
|
||||
{toasts.add({
|
||||
title: 'Welcome',
|
||||
duration: 5000,
|
||||
type: 'success',
|
||||
placement: 'bottom-center',
|
||||
showProgress: true
|
||||
})}
|
||||
</div>
|
||||
|
||||
<!-- Main Card -->
|
||||
<Section label="[About]">
|
||||
<Card>
|
||||
<h2 slot="headerLeft">{info.name}</h2>
|
||||
<h2 slot="headerRight">{info.job_title}</h2>
|
||||
<div slot="content" class="flex flex-row items-center gap-5">
|
||||
<img
|
||||
src={info.profile_photo}
|
||||
alt="Avatar"
|
||||
class="max-md:hidden rounded-full w-32 h-32 md:w-48 md:h-48 mt-2 mb-2 p-2 border-3"
|
||||
/>
|
||||
<p
|
||||
class="[&>*]:underline [&>*]:decoration-2 [&>*]:decoration-transparent [&>*]:hover:decoration-inherit [&>*]:transition-all [&>*]:duration-300 [&>*]:text-green-600"
|
||||
>
|
||||
{@html info.about}
|
||||
</p>
|
||||
</div>
|
||||
<h3 slot="footerLeft">{@html info.location}</h3>
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
<!-- Skills -->
|
||||
<Section label="[Skills]">
|
||||
<GridGallery>
|
||||
{#each info.skills as skill}
|
||||
<Card
|
||||
containerStyle="opacity-100 hover:opacity-100 hover:scale-[105%] md:opacity-70 transition-all duration-300"
|
||||
>
|
||||
<h2 slot="headerLeft">{skill.name}</h2>
|
||||
<i slot="headerRight" class="text-slate-300 text-5xl {skill.logo}"></i>
|
||||
<div slot="content">
|
||||
<Collapsible>
|
||||
<span slot="label" class="text-lg">About {skill.name}</span>
|
||||
<span slot="content">{skill.about}</span>
|
||||
</Collapsible>
|
||||
<SkillProgress skillColour={skill.colour} value={skill.competency} />
|
||||
</div>
|
||||
<h3 slot="footerLeft"><a href={skill.link} target="_blank">{skill.link}</a></h3>
|
||||
</Card>
|
||||
{/each}
|
||||
</GridGallery>
|
||||
</Section>
|
||||
|
||||
<!-- Experience -->
|
||||
<Section label="[Experience]">
|
||||
<Timeline timelineData={info.timeline} />
|
||||
</Section>
|
||||
{:catch}
|
||||
<div style="display: none;">
|
||||
{toasts.add({
|
||||
title: 'Error',
|
||||
description: 'There was an error loading static site data',
|
||||
duration: 0,
|
||||
placement: 'bottom-center',
|
||||
showProgress: true
|
||||
})}
|
||||
</div>
|
||||
{/await}
|
||||
|
@@ -1,60 +1,28 @@
|
||||
<script lang="ts">
|
||||
import Toasts from "$lib/components/Toasts/Toasts.svelte";
|
||||
import ThemeSwitcher from "$lib/components/ThemeSwitcher.svelte";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.main-container {
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.main-container {
|
||||
margin: 0em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
font-size: 110%;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1.5em;
|
||||
padding: 0em 0em 1.5em 0em;
|
||||
z-index: 2;
|
||||
height: 1.5em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fade {
|
||||
-webkit-animation: fadeinout 1s linear forwards;
|
||||
animation: fadeinout 1s linear forwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeinout {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeinout {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav>
|
||||
<a href = "/">//Profile</a>
|
||||
<a href = "/repos">//Repos</a>
|
||||
<a href = "/contact">//Contact</a>
|
||||
<ThemeSwitcher />
|
||||
</nav>
|
||||
|
||||
<div class="main-container fade">
|
||||
<Toasts />
|
||||
<slot />
|
||||
</div>
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { ToastContainer, FlatToast } from 'svelte-toasts';
|
||||
import { PageIcon } from '@luke-else/component-lib';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="min-h-screen px-8 py-4 bg-white text-slate-600 dark:bg-slate-900/90 dark:text-slate-200/60 md:text-2xl sm:text-md font-mono flex flex-col gap-5 transition duration-1000 ease-in-out"
|
||||
>
|
||||
<nav
|
||||
class="w-full px-8 py-4 flex gap-10 text-xl justify-center items-center text-green-600 font-semibold"
|
||||
>
|
||||
<a href="/" class="hover:underline">//Profile</a>
|
||||
<a href="/repos" class="hover:underline">//Repos</a>
|
||||
<a href="/contact" class="hover:underline">//Contact</a>
|
||||
</nav>
|
||||
|
||||
<a href="https://git.luke-else.co.uk" target="_blank">
|
||||
<PageIcon iconClass="devicon-git-plain" />
|
||||
</a>
|
||||
|
||||
<div class="container mx-auto justify-center items-center flex flex-col">
|
||||
<slot />
|
||||
<ToastContainer let:data>
|
||||
<FlatToast {data} />
|
||||
</ToastContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import Main from '../main.svelte';
|
||||
</script>
|
||||
|
||||
<Main></Main>
|
||||
<script>
|
||||
import Main from '../main.svelte';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Main></Main>
|
||||
</div>
|
@@ -1,82 +1,103 @@
|
||||
<script lang="ts">
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import { Toast, ToastType } from "$lib/toast";
|
||||
import { addToast } from "$lib/store";
|
||||
|
||||
import { page } from '$app/stores';
|
||||
const sent = $page.url.searchParams.get('sent');
|
||||
|
||||
if (sent == "true") {
|
||||
addToast(new Toast("Thank you! Your E-Mail has been sent. I will reply as soon as possible!", ToastType.Success, true, 5000));
|
||||
}
|
||||
// Can't use else otherwise the warning will display on load
|
||||
if (sent == "false") {
|
||||
addToast(new Toast("Sorry, your E-Mail could not be sent... Please try again later!", ToastType.Error, true, 5000));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 2 1;
|
||||
align-items: center;
|
||||
margin: 1em;
|
||||
gap: 1em 3em;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
padding: 1em 0em 1em 1em;
|
||||
background-color: var(--input);
|
||||
color: var(--fg);
|
||||
border: 0;
|
||||
outline: 0;
|
||||
border-radius: 8px;
|
||||
resize: none;
|
||||
min-width: 100%;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:focus, &:hover{
|
||||
box-shadow: .2em .2em .2em var(--green);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 2 1 5rem;
|
||||
flex-direction: column;
|
||||
gap: 1em 1em;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Card>
|
||||
<div slot="header">
|
||||
<h2>Contact</h2>
|
||||
</div>
|
||||
<div slot="content">
|
||||
<form action="https://api.staticforms.xyz/submit" method="post">
|
||||
<div class="container">
|
||||
<input type="hidden" name="accessKey" value="fbb5ec04-506b-448a-a445-a2e47579a966">
|
||||
<input type="text" name="name" placeholder="Name" required>
|
||||
<input type="text" name="email" placeholder="Email address" required>
|
||||
<input type="hidden" name="replyTo" value="@">
|
||||
<input type="text" name="subject" placeholder="Subject" required>
|
||||
<input type="text" name="honeypot" style="display: none;">
|
||||
</div>
|
||||
<div class="container">
|
||||
<textarea name="message" id="message" placeholder="Message" required></textarea>
|
||||
</div>
|
||||
<input class="button" type="submit" value="Send" />
|
||||
<input type="hidden" name="redirectTo" value="https://luke-else.co.uk/contact?sent=true">
|
||||
</form>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<a href="/Luke Else - CV.pdf" target="_blank" rel="noopener noreferrer">Curriculum Vitae</a>
|
||||
<a href="mailto:contact@luke-else.co.uk">E-Mail</a>
|
||||
</div>
|
||||
</Card>
|
||||
<script lang="ts">
|
||||
import { toasts } from 'svelte-toasts';
|
||||
import { Card, Section } from '@luke-else/component-lib';
|
||||
|
||||
import { page } from '$app/state';
|
||||
const sent = page.url.searchParams.get('sent');
|
||||
|
||||
if (sent == 'true') {
|
||||
toasts.add({
|
||||
title: 'Message sent!',
|
||||
description: 'Thank you for contacting me.',
|
||||
type: 'success',
|
||||
duration: 4000,
|
||||
placement: 'bottom-center'
|
||||
});
|
||||
}
|
||||
|
||||
if (sent == 'false') {
|
||||
toasts.add({
|
||||
title: 'Message not sent!',
|
||||
description: 'Please try again later.',
|
||||
type: 'error',
|
||||
duration: 4000,
|
||||
placement: 'bottom-center'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Section label="[Contact]">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<Card>
|
||||
<div slot="headerLeft">
|
||||
Contact Me
|
||||
</div>
|
||||
<!-- Contact Form -->
|
||||
<form slot="content" class="w-full max-w-3xl mx-auto flex flex-col gap-4 text-lg" action="https://api.staticforms.xyz/submit" method="post">
|
||||
<div class="hidden">
|
||||
<input type="hidden" name="accessKey" value="fbb5ec04-506b-448a-a445-a2e47579a966">
|
||||
<input type="hidden" name="replyTo" value="@">
|
||||
<input type="text" name="honeypot" style="display: none;">
|
||||
<input type="hidden" name="redirectTo" value="https://luke-else.co.uk/contact?sent=true">
|
||||
</div>
|
||||
<div class="flex flex-row md:flex-row gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium mb-1" for="name">Name</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-gray-400 px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-600 transition placeholder-gray-400"
|
||||
required
|
||||
placeholder="Your name"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium mb-1" for="email">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
class="w-full rounded-lg border border-gray-400 px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-600 transition placeholder-gray-400"
|
||||
required
|
||||
placeholder="you@email.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium mb-1" for="subject">Subject</label>
|
||||
<input
|
||||
id="subject"
|
||||
name="subject"
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-gray-400 px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-600 transition placeholder-gray-400"
|
||||
required
|
||||
placeholder="Subject"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium mb-1" for="message">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
class="w-full rounded-lg border border-gray-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-600 transition min-h-[80px] placeholder-gray-400"
|
||||
required
|
||||
placeholder="Your message"
|
||||
></textarea>
|
||||
</div>
|
||||
<!-- reCAPTCHA integration -->
|
||||
<div class="">
|
||||
<div class="g-recaptcha" data-sitekey="6LfjQAwrAAAAAIF57u8Wt4w5L5vBEWi5DfXXBuGy"></div>
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="self-end bg-blue-600 hover:bg-blue-700 text-white font-semibold py-1.5 px-8 rounded-lg transition"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
</Card>
|
||||
</Section>
|
||||
|
@@ -1,3 +1,73 @@
|
||||
<h1>Repos</h1>
|
||||
<p>Stay tuned! This is still in development.</p>
|
||||
<p>Come back later to find out what projects I'm currently working on!</p>
|
||||
<script lang="ts">
|
||||
import { loadRepos, repos } from '$lib/stores';
|
||||
import { timeSince, checkImage, IMAGE_URL_SUFFIX } from '$lib/api/git';
|
||||
import { toasts } from 'svelte-toasts';
|
||||
|
||||
import { GridGallery, Card, Loading, Section, Collapsible } from '@luke-else/component-lib';
|
||||
|
||||
let repoImages: Record<string, string | null> = {};
|
||||
|
||||
// When repos load, check for images
|
||||
$: if ($repos.length) {
|
||||
(async () => {
|
||||
for (const repo of $repos) {
|
||||
if (repoImages[repo.name] === undefined) {
|
||||
const url = repo.html_url + IMAGE_URL_SUFFIX;
|
||||
repoImages[repo.name] = (await checkImage(repo)) ? url : null;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
<Section label="[Repositories]">
|
||||
{#await loadRepos()}
|
||||
<Loading />
|
||||
{:then _}
|
||||
{#if $repos.length == 0}
|
||||
{console.log('No Repos')}
|
||||
<div style="display: none;">
|
||||
{toasts.add({
|
||||
title: 'Error',
|
||||
description: 'Failed to load repositories',
|
||||
duration: 5000,
|
||||
type: 'error',
|
||||
placement: 'bottom-center',
|
||||
showProgress: true
|
||||
})}
|
||||
</div>
|
||||
<p>Sorry... we can't show you anything here</p>
|
||||
{/if}
|
||||
<!-- Repositories loaded successfully -->
|
||||
<GridGallery>
|
||||
{#each $repos as repo}
|
||||
<!-- <Loading /> -->
|
||||
<Card
|
||||
containerStyle="opacity-100 hover:opacity-100 hover:scale-[105%] md:opacity-70 transition-all duration-300"
|
||||
>
|
||||
<h2 slot="headerLeft">{repo.name}</h2>
|
||||
<h2 slot="headerRight" class="text-sm text-gray-500">
|
||||
{repo.language}
|
||||
</h2>
|
||||
<div class="flex flex-col gap-5" slot="content">
|
||||
{repo.description}
|
||||
{#if repoImages[repo.name]}
|
||||
<Collapsible>
|
||||
<span slot="label" class="text-lg">See More</span>
|
||||
<!-- svelte-ignore a11y_img_redundant_alt -->
|
||||
<img
|
||||
slot="content"
|
||||
src={repoImages[repo.name]}
|
||||
alt="repo image"
|
||||
class=""
|
||||
/>
|
||||
</Collapsible>
|
||||
{/if}
|
||||
</div>
|
||||
<h3 slot="footerLeft">
|
||||
Last Updated: {timeSince(repo.updated_at)}
|
||||
</h3>
|
||||
</Card>
|
||||
{/each}
|
||||
</GridGallery>
|
||||
{/await}
|
||||
</Section>
|
||||
|
@@ -1,59 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let skills: any;
|
||||
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
|
||||
let showModal: boolean = false;
|
||||
let activeModal: any = null;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-footer {
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
gap: 1.5em;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
color: var(--fg);
|
||||
font-size: 3em;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#each skills as skill}
|
||||
<Card on:click={() => {showModal = true; activeModal = skill}}>
|
||||
<div slot="header">
|
||||
<h2>{skill.skill}</h2>
|
||||
<i class="{skill.logo} logo"></i>
|
||||
</div>
|
||||
<div slot="content">
|
||||
<p class="not-required">{@html skill.usage}</p>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<!-- svelte-ignore a11y-invalid-attribute -->
|
||||
<a href="#">View More</a>
|
||||
<a href="/repos">Repos</a>
|
||||
</div>
|
||||
</Card>
|
||||
{/each}
|
||||
|
||||
<!--Modal to be displayed on click-->
|
||||
{#if activeModal != null}
|
||||
<Modal bind:showModal>
|
||||
<h2 slot="header" class="card-header">
|
||||
{activeModal.skill}
|
||||
<i class="{activeModal.logo} logo"></i>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
{activeModal.about}
|
||||
</p>
|
||||
|
||||
<div class="card-footer">
|
||||
<a href="{activeModal.link}" target="_blank" rel="noopener noreferrer">Learn More</a>
|
||||
<a href="/repos">Repos</a>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
@@ -1,63 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let timelineData: any;
|
||||
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import TimelineItem from '$lib/components/timeline/TimelineItem.svelte';
|
||||
import TimelineSeparator from '$lib/components/timeline/TimelineSeparator.svelte';
|
||||
import TimelineDot from '$lib/components/timeline/TimelineDot.svelte';
|
||||
import TimelineConnector from '$lib/components/timeline/TimelineConnector.svelte';
|
||||
import TimelineContent from '$lib/components/timeline/TimelineContent.svelte';
|
||||
import TimelineOppositeContent from '$lib/components/timeline/TimelineOppositeContent.svelte';
|
||||
</script>
|
||||
|
||||
<Timeline
|
||||
position="alternate"
|
||||
style={`
|
||||
border-radius: 3%;
|
||||
padding: 1rem;
|
||||
`}
|
||||
>
|
||||
{#each timelineData as item}
|
||||
<TimelineItem>
|
||||
<TimelineOppositeContent slot="opposite-content">
|
||||
<p class="oposite-content-title">{item.duration}</p>
|
||||
</TimelineOppositeContent>
|
||||
|
||||
<TimelineSeparator>
|
||||
{#if item.duration.includes('Present') || !item.duration.includes('-')}
|
||||
<div class="elementToFadeInAndOut">
|
||||
<TimelineDot style={`background-color: var(--link); border-color: var(--bg-grad-2);`} />
|
||||
</div>
|
||||
{:else}
|
||||
<TimelineDot style={`background-color: var(--link); border-color: var(--bg-grad-2);`} />
|
||||
{/if}
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent>
|
||||
<h3 class="content-title">{item.title}</h3>
|
||||
<p class="content-description">{@html item.description}</p>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
{/each}
|
||||
</Timeline>
|
||||
|
||||
<style>
|
||||
.oposite-content-title {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--bg-grad-2);
|
||||
}
|
||||
.content-title {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content-description {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
color: var(--fg);
|
||||
font-weight: lighter;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
</style>
|
@@ -1,60 +1,132 @@
|
||||
{
|
||||
"name": "Luke Else",
|
||||
"job_title": "Software Engineer",
|
||||
"profile_photo": "/profile.jpg",
|
||||
"skills" : [
|
||||
{
|
||||
"skill": "Rust",
|
||||
"logo": "devicon-rust-plain",
|
||||
"link": "https://rust-lang.org",
|
||||
"usage": "Rust is a memory safe programming language that relies on a borrow checker to keep track of who owns memory. This makes it versatile in embedded software becuase of the zero cost abstraction and lack of requirement for a garbage colllector. Becuase of this, I opted to use Rust for one of my own projects in which I created a <a href='https://git@git.luke-else.co.uk/luke-else/esp32_gps_display'>GPS fueled speedometer</a> and position tracker to act as a form of telematics for my car. I have also used Rust when creating some software to assist with university studies. See here for my <a href='https://git@git.luke-else.co.uk/luke-else/subnet_calculator'>Subnet Calculator</a>.",
|
||||
"about": "Rust is a remarkable programming language that combines the best of both high-level and low-level programming. It prioritizes safety and efficiency through strict memory management and concurrent programming features, all while maintaining clean and readable code. The active Rust community makes it a valuable choice for building reliable software."
|
||||
},
|
||||
{
|
||||
"skill": "C++",
|
||||
"logo": "devicon-cplusplus-plain",
|
||||
"link": "https://cplusplus.com/",
|
||||
"usage": "Opposing Rust, C++, has been my primary language since joining Thales in late 2022. I have predominantly been working on a distributed simulation system that relies upon <a href='https://en.wikipedia.org/wiki/Inter-process_communication'>IPC</a> through the use of internal tools to orchestrate itself and the platform's sessions. I've had a lot of experience using both <a href='https://www.qt.io'>QT</a>, as well as the lesser known <a href='https://github.com/ocornut/imgui'>ImGui</a>, both of which have given me the platform to build readily deployable apps to customers at Thales UK.",
|
||||
"about": "C++, a versatile and powerful programming language, has stood the test of time as a cornerstone of software development. Its combination of high-level abstractions and low-level control allows developers to tackle a wide range of projects efficiently. With a rich standard library and a massive ecosystem of libraries and frameworks; C++ empowers programmers to create efficient and scalable software solutions."
|
||||
},
|
||||
{
|
||||
"skill": "Git",
|
||||
"logo": "devicon-git-plain",
|
||||
"link": "https://git-scm.com",
|
||||
"usage": "With git being pretty much the 'Defaqto standard' when it comes to version control systems, I have had a lot of experience with its use, including more advanced features such as <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Branching / Merging'</a>, <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Hooks'</a>, <a href='https://www.atlassian.com/git/tutorials/advanced-overview'>'Stashing'</a> etc... I've further setup my own <a href='https://git.luke-else.co.uk/luke-else/'>services</a> for hosting remote Git reporitories to allow for complete access and control over my work. Appending to this, I have setup my own form of actions to aid in the actioning of <a href='https://www.redhat.com/en/topics/devops/what-is-ci-cd'>CI/CD</a> on my own projects.",
|
||||
"about": "Git, a fundamental tool in version control, streamlines collaboration and code management. Its simplicity and robustness empower developers to track changes, collaborate seamlessly, and maintain code efficiently. With Git, managing and tracking code changes becomes a breeze, making it an essential tool for software development."
|
||||
},
|
||||
{
|
||||
"skill": "Docker",
|
||||
"logo": "devicon-docker-plain",
|
||||
"link": "https://docker.com",
|
||||
"usage": "With docker being so versatile when it comes to deploying software to end users, I decided to pick it up as a skill in order to improve the quality of applications and services I can offer to individuals. I use docker alongside its child, 'docker-compose' to control the <a href='https://git.luke-else.co.uk/luke-else/server'>provisioning of containers</a> on my main home-lab server which delivers content like <a href='https://git.luke-else.co.uk/luke-else/luke-else.co.uk'>this website</a> as well as database and <a href='https://git.luke-else.co.uk/luke-else/'>remote git services</a>.",
|
||||
"about": "Docker, a transformative containerization platform, simplifies application deployment and management. It encapsulates applications and their dependencies in lightweight containers, providing consistency across different environments. Docker's user-friendly approach makes it accessible to developers and DevOps professionals, streamlining the software development and deployment process. Its efficiency, scalability, and support for microservices architecture have made it a go-to tool for creating, deploying, and scaling applications. Docker has revolutionized the way we think about software packaging and distribution, making it an essential component of modern software development workflows."
|
||||
},
|
||||
{
|
||||
"skill": "Svelte",
|
||||
"logo": "devicon-svelte-plain",
|
||||
"link": "https://svelte.dev",
|
||||
"usage": "Svelte is a front and backend web framework that allows for stylised components to be reused in an efficient manor. This <a href='https://git.luke-else.co.uk/luke-else/luke-else.co.uk'>website</a> was made using svelte and has given me a great opportunity to learn about Svelte's power and usage. I further want to expand this in the future by using <a href='https://github.com/tauri-apps/tauri'>Tauri</a> to create rendered web application in containers that can be deployed as desktop apps.",
|
||||
"about": "Svelte is an impressive web framework that stands out in the world of front-end development. It's known for its unique approach, compiling components to highly optimized vanilla JavaScript, resulting in blazing-fast web applications. Svelte's simplicity and declarative syntax make it easy to learn and use, and it encourages efficient, maintainable code. "
|
||||
}
|
||||
],
|
||||
"about": "Hello! I'm an enthusiastic, dedicated software engineer passionate about backend development, networking, and embedded systems. I am currently employed at <a href='https://www.thalesgroup.com/en'>Thales UK</a> and thrive on architecting robust backend solutions, optimizing data transmission, and crafting efficient embedded software. I love tackling complex challenges, collaborating with fellow professionals, and staying up-to-date with tech trends such as my current venture in learning <a href='https://rust-lang.org'>Rust-Lang</a>.",
|
||||
"timeline" : [
|
||||
{
|
||||
"duration" : "September 2022 - Present",
|
||||
"title" : "Thales UK - Software Engineer",
|
||||
"description" : "As a software engineering apprentice at Thales UK, I find myself partaking in agile / scrum development methodologies in a strong team of 6 other engineers. The team iterates on a pre-existing system designed for the MOD, written in C++, using internal frameworks to assist. <br /><br /> To extend this, the apprenticeship includes allocated time for studying a Digital and Thechnology Solutions degree with the University of Warwick, including modules relevant to business management, devlopment processes and data integrity etc..."
|
||||
},
|
||||
{
|
||||
"duration" : "September 2020 - July 2022",
|
||||
"title" : "The Norton Knatchbull School (A-Levels)",
|
||||
"description" : "Computer Science (<b>A*</b>) <br /> Mathematics (<b>A</b>) <br /> Physics (<b>A</b>)"
|
||||
},
|
||||
{
|
||||
"duration" : "September 2015 - July 2020",
|
||||
"title" : "The Norton Knatchbull School (GCSEs)",
|
||||
"description" : "FSMQ (C) <br /> Maths (8) <br /> Geography (<b>9</b>) <br /> Biology (<b>9</b>) <br /> Chemistry (<b>9</b>) <br /> Physics (<b>9</b>) <br /> Spanish (7) <br /> English (Literature & Language) (7, 7) <br /> Computer Science (<b>9</b>)"
|
||||
}
|
||||
]
|
||||
{
|
||||
"name": "Luke Else",
|
||||
"job_title": "Software Engineer",
|
||||
"location": "Crawley, Sussex <br /> UK",
|
||||
"profile_photo": "/profile.jpg",
|
||||
"about": "Hello! I'm an enthusiastic, dedicated software engineer passionate about backend development, networking, and embedded systems. I am currently employed at <a href='https://www.thalesgroup.com/en'>Thales UK</a> and thrive on architecting robust backend solutions, optimizing data transmission, and crafting efficient embedded software. I love tackling complex challenges, collaborating with fellow professionals, and staying up-to-date with tech trends such as my current venture in learning <a href='https://rust-lang.org'>Rust-Lang</a>.",
|
||||
"skills": [
|
||||
{
|
||||
"name": "Rust",
|
||||
"logo": "devicon-rust-plain",
|
||||
"colour": "bg-orange-400",
|
||||
"link": "https://rust-lang.org",
|
||||
"about": "Rust combines safety, efficiency, and clean code, making it a powerful choice for reliable software development.",
|
||||
"competency": 70
|
||||
},
|
||||
{
|
||||
"name": "C++",
|
||||
"logo": "devicon-cplusplus-plain",
|
||||
"colour": "bg-blue-400",
|
||||
"link": "https://cplusplus.com/",
|
||||
"about": "C++ offers high-level abstractions with low-level control, making it essential for performance-critical applications.",
|
||||
"competency": 80
|
||||
},
|
||||
{
|
||||
"name" : "Python",
|
||||
"logo": "devicon-python-plain",
|
||||
"colour": "bg-yellow-400",
|
||||
"link": "https://python.org",
|
||||
"about": "Python is a versatile language known for its simplicity and readability, making it ideal for rapid development and data analysis.",
|
||||
"competency": 70
|
||||
},
|
||||
{
|
||||
"name": "Git",
|
||||
"logo": "devicon-git-plain",
|
||||
"colour": "bg-red-400",
|
||||
"link": "https://git-scm.com",
|
||||
"about": "Git is an essential tool for version control, enabling efficient collaboration and streamlined code management.",
|
||||
"competency": 80
|
||||
},
|
||||
{
|
||||
"name": "Docker",
|
||||
"logo": "devicon-docker-plain",
|
||||
"colour": "bg-blue-500",
|
||||
"link": "https://docker.com",
|
||||
"about": "Docker simplifies deployment by packaging applications in lightweight containers, ensuring consistency across environments.",
|
||||
"competency": 100
|
||||
},
|
||||
{
|
||||
"name": "Kubernetes",
|
||||
"logo": "devicon-kubernetes-plain",
|
||||
"colour": "bg-blue-600",
|
||||
"link": "https://kubernetes.io",
|
||||
"about": "Kubernetes automates the deployment, scaling, and management of containerized applications, enhancing operational efficiency.",
|
||||
"competency": 40
|
||||
},
|
||||
{
|
||||
"name": "PostgreSQL",
|
||||
"logo": "devicon-postgresql-plain",
|
||||
"colour": "bg-blue-700",
|
||||
"link": "https://postgresql.org",
|
||||
"about": "PostgreSQL is a powerful, open-source relational database known for its robustness and advanced features.",
|
||||
"competency": 70
|
||||
},
|
||||
{
|
||||
"name": "MongoDB",
|
||||
"logo": "devicon-mongodb-plain",
|
||||
"colour": "bg-green-500",
|
||||
"link": "https://mongodb.com",
|
||||
"about": "MongoDB is a NoSQL database that provides flexibility and scalability for modern applications with unstructured data.",
|
||||
"competency": 70
|
||||
},
|
||||
{
|
||||
"name": "Redis",
|
||||
"logo": "devicon-redis-plain",
|
||||
"colour": "bg-red-600",
|
||||
"link": "https://redis.io",
|
||||
"about": "Redis is an in-memory data structure store, used as a database, cache, and message broker for high-performance applications.",
|
||||
"competency": 30
|
||||
},
|
||||
{
|
||||
"name": "JavaScript",
|
||||
"logo": "devicon-javascript-plain",
|
||||
"colour": "bg-yellow-500",
|
||||
"link": "https://javascript.com",
|
||||
"about": "JavaScript is a versatile language that powers dynamic web applications and enhances user interactivity.",
|
||||
"competency": 60
|
||||
},
|
||||
{
|
||||
"name": "Tailwind CSS",
|
||||
"logo": "devicon-tailwindcss-plain",
|
||||
"colour": "bg-blue-800",
|
||||
"link": "https://tailwindcss.com/",
|
||||
"about": "Tailwind CSS is a utility-first CSS framework that enables rapid UI development with a focus on customization and responsiveness.",
|
||||
"competency": 60
|
||||
},
|
||||
{
|
||||
"name": "Svelte",
|
||||
"logo": "devicon-svelte-plain",
|
||||
"colour": "bg-orange-400",
|
||||
"link": "https://svelte.dev",
|
||||
"about": "Svelte compiles to optimized JavaScript, offering a fast, efficient, and maintainable front-end development experience.",
|
||||
"competency": 55
|
||||
}
|
||||
],
|
||||
"timeline" : [
|
||||
{
|
||||
"duration" : "April 2025 - Present",
|
||||
"title" : "Thales UK (DDCC) - Software Engineer",
|
||||
"description" : "As a 3rd year apprentice at Thales UK’s Digital Data Competency Centre, I have taken on responsibility for developing microservices that encapsulate Machine Learning models provided by R&D teams, helping to advance product readiness. These services are primarily written in Python and deployed to Kubernetes clusters for use across the business. Our team also designs and maintains CI/CD pipelines to automate the deployment of both these services and their supporting infrastructure."
|
||||
},
|
||||
{
|
||||
"duration" : "September 2022 - April 2025",
|
||||
"title" : "Thales UK (ISR) - Software Engineer",
|
||||
"description" : "As a software engineering apprentice at Thales UK, Intelligence Surveillance and Reconnaissance, I worked within an agile team of six engineers, contributing to the ongoing development of a C++ system for the MOD. My role involved collaborating closely with colleagues, following Scrum methodologies, and leveraging internal frameworks to enhance and maintain the existing platform."
|
||||
},
|
||||
{
|
||||
"duration" : "September 2022 - Present",
|
||||
"title" : "University of Warwick - Digital and Technology Solutions",
|
||||
"description" : "The apprenticeship includes allocated time for studying a Digital and Thechnology Solutions degree with the University of Warwick, including modules relevant to business management, devlopment processes and data integrity etc..."
|
||||
},
|
||||
{
|
||||
"duration" : "September 2020 - July 2022",
|
||||
"title" : "The Norton Knatchbull School (A-Levels)",
|
||||
"description" : "Computer Science (<b>A*</b>) <br /> Mathematics (<b>A</b>) <br /> Physics (<b>A</b>)"
|
||||
},
|
||||
{
|
||||
"duration" : "September 2015 - July 2020",
|
||||
"title" : "The Norton Knatchbull School (GCSEs)",
|
||||
"description" : "Computer Science (<b>9</b>) <br /> Physics (<b>9</b>) <br /> Chemistry (<b>9</b>) <br /> Biology (<b>9</b>) <br /> Geography (<b>9</b>) <br /> FSMQ (C) <br /> Maths (8) <br /> Spanish (7) <br /> English (Literature & Language) (7, 7) <br />"
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 425 KiB After Width: | Height: | Size: 67 KiB |
@@ -1,18 +0,0 @@
|
||||
:root {
|
||||
--bg: #282c34;
|
||||
--bg-secondary: #474d57;
|
||||
--bg-grad-1: #484e58;
|
||||
--bg-grad-2: #4e5560;
|
||||
--bg-grad-3: #59616d;
|
||||
--bg-grad-4: #606a7b;
|
||||
--bg-grad-5: #606978;
|
||||
--input: #4e5560;
|
||||
--fg: #ABB2BF;
|
||||
--header: #E06C75;
|
||||
--link: #98C379;
|
||||
--hover: #56B6C2;
|
||||
--glow: #C678DD;
|
||||
|
||||
--green: #98C379;
|
||||
--red: #E06C75;
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
:root {
|
||||
--bg: #fff;
|
||||
--bg-secondary: #ebebeb;
|
||||
--bg-grad-1: #c1c1c1;
|
||||
--bg-grad-2: #a1a1a1;
|
||||
--bg-grad-3: #858585;
|
||||
--bg-grad-4: #616161;
|
||||
--bg-grad-5: #484848;
|
||||
--input: #a9a9a9;
|
||||
--fg: #2f2f2f;
|
||||
--header: #514a4a;
|
||||
--link: #df0000;
|
||||
--hover: #4f4b489b;
|
||||
--glow: #545454;
|
||||
|
||||
--green: #98C379;
|
||||
--red: #E06C75;
|
||||
}
|
@@ -1,19 +1,10 @@
|
||||
// import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
// import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: { adapter: adapter() }
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
sveltekit(),
|
||||
]
|
||||
});
|
||||
|