Compare commits
No commits in common. "gh-pages" and "main" have entirely different histories.
|
|
@ -0,0 +1,4 @@
|
||||||
|
// TODO: Follow https://www.tommyp.org/blog/adding-spotify-now-playing-to-astro and remove this comment
|
||||||
|
CLIENT_ID=
|
||||||
|
CLIENT_SECRET=
|
||||||
|
REFRESH_TOKEN=
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
name: Deploy site to GitHub Pages
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # ✅ allow pushing to branches
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./dist
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Astro
|
||||||
|
.astro
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
.output
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for (testing) VSCode extensions
|
||||||
|
.vscode
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Netlify
|
||||||
|
.netlify
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Build and generated files
|
||||||
|
*.min.*
|
||||||
|
*.bundle.*
|
||||||
|
*.map
|
||||||
|
|
||||||
|
# Git and version control
|
||||||
|
**/.git
|
||||||
|
|
||||||
|
# Framework-specific files
|
||||||
|
next-env.d.ts
|
||||||
|
__generated__/
|
||||||
|
|
||||||
|
# Test and coverage files
|
||||||
|
coverage/
|
||||||
|
*.spec.*
|
||||||
|
*.test.*
|
||||||
|
|
||||||
|
# Editor-specific files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Markdown and MDX files
|
||||||
|
*.md
|
||||||
|
*.mdx
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
.changeset
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-astro", "@trivago/prettier-plugin-sort-imports"],
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.astro",
|
||||||
|
"options": {
|
||||||
|
"parser": "astro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"importOrder": [
|
||||||
|
"<THIRD_PARTY_MODULES>",
|
||||||
|
"^@internal/(.*)$",
|
||||||
|
"^@/models/(.*)$",
|
||||||
|
"^@/utils/(.*)$",
|
||||||
|
"^@/components/(?!ui/)(.*)$",
|
||||||
|
"^@/components/ui/(.*)$",
|
||||||
|
"^[./].*(?<!\\.(c|le|sc)ss)$",
|
||||||
|
"^[.]/[-a-zA-Z0-9_]+[.](module)[.](css|scss|less)$"
|
||||||
|
],
|
||||||
|
"importOrderSeparation": true,
|
||||||
|
"importOrderSortSpecifiers": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Kristian Emil Takvam
|
||||||
|
Copyright (c) 2024-present, trueberryless
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div align="center">
|
||||||
|
<img src="src/assets/banner.png" style="width:80%;" alt="banner" align="center" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://github.com/Kremtastic/Kremtastic.github.io/blob/main/LICENSE) [](https://github.com/Kremtastic/Kremtastic.github.io)  [](https://kremtastic.github.io/)
|
||||||
|
|
||||||
|
Heres the repo for my personal website!
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
###### *Built with the <a href="https://github.com/trueberryless-org/astro-portfolio-template" target="_blank">Astro-portfolio-template</a> theme by <a href="https://trueberryless.org/" target="_blank">@trueberryless</a> (MIT License)*
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import{r as c}from"./index.Cd_vQiNd.js";var h={exports:{}},x={};/**
|
|
||||||
* @license React
|
|
||||||
* react-jsx-runtime.production.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/var v;function m(){if(v)return x;v=1;var r=Symbol.for("react.transitional.element"),a=Symbol.for("react.fragment");function u(d,n,t){var s=null;if(t!==void 0&&(s=""+t),n.key!==void 0&&(s=""+n.key),"key"in n){t={};for(var l in n)l!=="key"&&(t[l]=n[l])}else t=n;return n=t.ref,{$$typeof:r,type:d,key:s,ref:n!==void 0?n:null,props:t}}return x.Fragment=a,x.jsx=u,x.jsxs=u,x}var g;function y(){return g||(g=1,h.exports=m()),h.exports}var e=y();const j=({progress:r,isPlaying:a})=>{const[u,d]=c.useState([]);c.useEffect(()=>{const t=[];for(let s=0;s<50;s++)t.push(Math.floor(Math.random()*800)+700);d(t)},[]);const n=()=>{const t=Math.floor(r/2),s=[],l=a?3:8,i=a?25:20;for(let o=1;o<51;o++){const p=o<=t;s.push(e.jsx("line",{x1:o+o*6,y1:l,x2:o+o*6,y2:i,strokeWidth:"2",stroke:p?"var(--ctp-green)":"var(--ctp-overlay0)",style:{transformOrigin:"center",transition:"stroke 1s ease-in-out",animation:a?`sound ${u[o]}ms linear infinite alternate`:"none"}},o))}return s};return e.jsxs("div",{children:[e.jsx("svg",{width:"302",height:"28",viewBox:"0 0 344 28",fill:"none",xmlns:"http://www.w3.org/2000/svg",style:{maxWidth:"100%",height:"auto"},children:n()}),e.jsx("style",{children:`
|
|
||||||
@keyframes sound {
|
|
||||||
0% {
|
|
||||||
transform: scaleY(0.4);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scaleY(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`})]})},k=()=>{const[r,a]=c.useState(null),[u,d]=c.useState(!1),[n,t]=c.useState(!0),[s,l]=c.useState(0);return c.useEffect(()=>{const i=async()=>{try{const p=await fetch("/api/spotify/now-playing");if(p.status===204){a(null),d(!1),t(!1);return}const f=await p.json();a(f),d(f.isPlaying),l(f.progress||0),t(!1)}catch(p){console.error("Error fetching now playing:",p),t(!1)}};i();const o=setInterval(i,1e4);return()=>clearInterval(o)},[]),n?e.jsx("div",{style:{textAlign:"center",color:"var(--ctp-subtext0)"},children:"Loading..."}):r?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"1.5rem"},children:[e.jsxs("div",{style:{display:"flex",gap:"1.5rem",alignItems:"center"},children:[r.albumArt&&e.jsx("img",{src:r.albumArt,alt:r.album,style:{width:"80px",height:"80px",borderRadius:"8px",border:"2px solid var(--ctp-green)"}}),e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontSize:"1.25rem",fontWeight:"600",color:"var(--ctp-text)",marginBottom:"0.25rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:r.title}),e.jsx("div",{style:{fontSize:"1rem",color:"var(--ctp-subtext0)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:r.artist}),r.album&&e.jsx("div",{style:{fontSize:"0.9rem",color:"var(--ctp-subtext1)",marginTop:"0.25rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:r.album})]})]}),e.jsx("div",{style:{display:"flex",justifyContent:"center"},children:e.jsx(j,{progress:s,isPlaying:u})}),r.trackUrl&&e.jsx("a",{href:r.trackUrl,target:"_blank",rel:"noopener noreferrer",style:{display:"inline-flex",alignItems:"center",gap:"0.5rem",padding:"0.75rem 1.5rem",backgroundColor:"var(--ctp-green)",color:"var(--ctp-base)",borderRadius:"9999px",textDecoration:"none",fontWeight:"500",alignSelf:"center",transition:"all 0.3s ease"},onMouseEnter:i=>{i.currentTarget.style.transform="scale(1.05)",i.currentTarget.style.backgroundColor="var(--ctp-teal)"},onMouseLeave:i=>{i.currentTarget.style.transform="scale(1)",i.currentTarget.style.backgroundColor="var(--ctp-green)"},children:"Open in Spotify"})]}):e.jsxs("div",{style:{textAlign:"center",color:"var(--ctp-subtext0)"},children:[e.jsx("svg",{width:"64",height:"64",viewBox:"0 0 24 24",fill:"none",style:{margin:"0 auto 1rem",color:"var(--ctp-green)"},children:e.jsx("path",{d:"M9 18V5l12-2v13M9 18c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3zm12-2c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})}),e.jsx("p",{children:"Not playing anything right now"})]})};export{k as default};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
.project[data-astro-cid-amng4zvp]{padding:6rem 2rem;display:flex;justify-content:center}.content[data-astro-cid-amng4zvp]{max-width:600px;width:100%}.section-title[data-astro-cid-amng4zvp]{font-size:2rem;font-weight:600;color:var(--ctp-text);margin-bottom:3rem;text-align:center}.projects[data-astro-cid-amng4zvp]{display:flex;flex-direction:column;gap:1.5rem}.project-link[data-astro-cid-amng4zvp]{display:flex;align-items:center;gap:1rem;padding:1.5rem 2rem;background-color:var(--ctp-surface0);border:2px solid var(--project-color);border-radius:12px;text-decoration:none;color:var(--ctp-text);transition:all .3s ease;position:relative;overflow:hidden}.project-link[data-astro-cid-amng4zvp]:before{content:"";position:absolute;inset:-1px;background-color:var(--project-color);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1);z-index:0}.project-link[data-astro-cid-amng4zvp]:hover:before{transform:scaleX(1)}.project-icon[data-astro-cid-amng4zvp]{font-size:1.5rem;color:var(--project-color);transition:all .3s ease;z-index:1}.project-name[data-astro-cid-amng4zvp]{flex:1;font-size:1.25rem;font-weight:500;z-index:1;transition:color .3s ease}.project-arrow[data-astro-cid-amng4zvp]{width:24px;height:24px;color:var(--project-color);transition:all .3s ease;z-index:1}.project-link[data-astro-cid-amng4zvp]:hover{transform:translate(8px)}.project-link[data-astro-cid-amng4zvp]:hover .project-icon[data-astro-cid-amng4zvp],.project-link[data-astro-cid-amng4zvp]:hover .project-arrow[data-astro-cid-amng4zvp],.project-link[data-astro-cid-amng4zvp]:hover .project-name[data-astro-cid-amng4zvp]{color:var(--ctp-base)}@media(max-width:600px){.project[data-astro-cid-amng4zvp]{padding:4rem 2rem}.section-title[data-astro-cid-amng4zvp]{font-size:1.5rem;margin-bottom:2rem}.project-link[data-astro-cid-amng4zvp]{padding:1.25rem 1.5rem}.project-name[data-astro-cid-amng4zvp]{font-size:1.1rem}}.spotify[data-astro-cid-a3qsdpjt]{padding:6rem 2rem;display:flex;justify-content:center}.content[data-astro-cid-a3qsdpjt]{max-width:600px;width:100%}.spotify-container[data-astro-cid-a3qsdpjt]{background-color:var(--ctp-surface0);border:2px solid var(--ctp-green);border-radius:12px;padding:2rem;min-height:200px}@media(max-width:600px){.spotify[data-astro-cid-a3qsdpjt]{padding:4rem 2rem}.spotify-container[data-astro-cid-a3qsdpjt]{padding:1.5rem}}.project[data-astro-cid-4lhuo7md]{padding:6rem 2rem;display:flex;justify-content:center}.content[data-astro-cid-4lhuo7md]{max-width:600px;width:100%}.section-title[data-astro-cid-4lhuo7md]{font-size:2rem;font-weight:600;color:var(--ctp-text);margin-bottom:3rem;text-align:center}.projects[data-astro-cid-4lhuo7md]{display:flex;flex-direction:column;gap:1.5rem}.project-link[data-astro-cid-4lhuo7md]{display:flex;align-items:center;gap:1rem;padding:1.5rem 2rem;background-color:var(--ctp-surface0);border:2px solid var(--project-color);border-radius:12px;text-decoration:none;color:var(--ctp-text);transition:all .3s ease;position:relative;overflow:hidden}.project-link[data-astro-cid-4lhuo7md]:before{content:"";position:absolute;inset:-1px;background-color:var(--project-color);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.4,0,.2,1);z-index:0}.project-link[data-astro-cid-4lhuo7md]:hover:before{transform:scaleX(1)}.project-icon[data-astro-cid-4lhuo7md]{font-size:1.5rem;color:var(--project-color);transition:all .3s ease;z-index:1}.project-name[data-astro-cid-4lhuo7md]{flex:1;font-size:1.25rem;font-weight:500;z-index:1;transition:color .3s ease}.project-arrow[data-astro-cid-4lhuo7md]{width:24px;height:24px;color:var(--project-color);transition:all .3s ease;z-index:1}.project-link[data-astro-cid-4lhuo7md]:hover{transform:translate(8px)}.project-link[data-astro-cid-4lhuo7md]:hover .project-icon[data-astro-cid-4lhuo7md],.project-link[data-astro-cid-4lhuo7md]:hover .project-arrow[data-astro-cid-4lhuo7md],.project-link[data-astro-cid-4lhuo7md]:hover .project-name[data-astro-cid-4lhuo7md]{color:var(--ctp-base)}@media(max-width:600px){.project[data-astro-cid-4lhuo7md]{padding:4rem 2rem}.section-title[data-astro-cid-4lhuo7md]{font-size:1.5rem;margin-bottom:2rem}.project-link[data-astro-cid-4lhuo7md]{padding:1.25rem 1.5rem}.project-name[data-astro-cid-4lhuo7md]{font-size:1.1rem}}
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// @ts-check
|
||||||
|
import netlify from "@astrojs/netlify";
|
||||||
|
import react from "@astrojs/react";
|
||||||
|
import { defineConfig, envField } from "astro/config";
|
||||||
|
|
||||||
|
import vtbot from "astro-vtbot";
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [react(), vtbot()],
|
||||||
|
adapter: netlify(),
|
||||||
|
env: {
|
||||||
|
schema: {
|
||||||
|
CLIENT_ID: envField.string({ context: "server", access: "secret" }),
|
||||||
|
CLIENT_SECRET: envField.string({ context: "server", access: "secret" }),
|
||||||
|
REFRESH_TOKEN: envField.string({ context: "server", access: "secret" }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/png" href="/profilePicture.png"><meta name="generator" content="Astro v5.14.5"><meta name="darkreader-lock"><title>Kristian Emil Takvam</title><style>:root{--ctp-rosewater: #f5e0dc;--ctp-flamingo: #f2cdcd;--ctp-pink: #f5c2e7;--ctp-mauve: #cba6f7;--ctp-red: #f38ba8;--ctp-maroon: #eba0ac;--ctp-peach: #fab387;--ctp-yellow: #f9e2af;--ctp-green: #a6e3a1;--ctp-teal: #94e2d5;--ctp-sky: #89dceb;--ctp-sapphire: #74c7ec;--ctp-blue: #89b4fa;--ctp-lavender: #b4befe;--ctp-text: #cdd6f4;--ctp-subtext1: #bac2de;--ctp-subtext0: #a6adc8;--ctp-overlay2: #9399b2;--ctp-overlay1: #7f849c;--ctp-overlay0: #6c7086;--ctp-surface2: #585b70;--ctp-surface1: #45475a;--ctp-surface0: #313244;--ctp-base: #1e1e2e;--ctp-mantle: #181825;--ctp-crust: #11111b}*{margin:0;padding:0;box-sizing:border-box}body{background-color:var(--ctp-base);color:var(--ctp-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;line-height:1.6}.intro[data-astro-cid-u43ozx4m]{padding:8rem 2rem 6rem;display:flex;justify-content:center}.content[data-astro-cid-u43ozx4m]{max-width:600px;width:100%;display:flex;align-items:center;gap:2rem}.pfp[data-astro-cid-u43ozx4m]{width:120px;height:120px;border-radius:50%;border:3px solid var(--ctp-lavender);transition:transform .3s ease,border-color .3s ease}.pfp[data-astro-cid-u43ozx4m]:hover{transform:scale(1.05);border-color:var(--ctp-pink)}.text[data-astro-cid-u43ozx4m]{flex:1}h1[data-astro-cid-u43ozx4m]{font-size:2rem;font-weight:600;color:var(--ctp-text);margin-bottom:.5rem}.subtitle[data-astro-cid-u43ozx4m]{font-size:1rem;color:var(--ctp-subtext0);opacity:.8}@media(max-width:600px){.intro[data-astro-cid-u43ozx4m]{padding:4rem 2rem 3rem}.content[data-astro-cid-u43ozx4m]{flex-direction:column;text-align:center}.pfp[data-astro-cid-u43ozx4m]{width:100px;height:100px}h1[data-astro-cid-u43ozx4m]{font-size:1.5rem}}.wave-container[data-astro-cid-5baqliqx]{width:100%;padding:3rem 2rem;display:flex;justify-content:center}.wave-wrapper[data-astro-cid-5baqliqx]{max-width:600px;width:100%;height:40px;overflow:hidden;position:relative;mask-image:linear-gradient(to right,transparent 0%,black 8%,black 92%,transparent 100%);-webkit-mask-image:linear-gradient(to right,transparent 0%,black 8%,black 92%,transparent 100%)}.wave[data-astro-cid-5baqliqx]{width:300%;height:100%;position:absolute;left:-100%}.wave-path[data-astro-cid-5baqliqx]{vector-effect:non-scaling-stroke}.wave-slide-left[data-astro-cid-5baqliqx]{animation:wave-slide-left var(--wave-speed) linear infinite}.wave-slide-right[data-astro-cid-5baqliqx]{animation:wave-slide-right var(--wave-speed) linear infinite}@keyframes wave-slide-left{0%{transform:translate(0)}to{transform:translate(-33.33%)}}@keyframes wave-slide-right{0%{transform:translate(-33.33%)}to{transform:translate(0)}}@media(max-width:600px){.wave-container[data-astro-cid-5baqliqx]{padding:2rem 1rem}}.socials[data-astro-cid-upu6fzxr]{padding:4rem 2rem 8rem;display:flex;justify-content:center}.content[data-astro-cid-upu6fzxr]{max-width:600px;width:100%;display:flex;justify-content:center}.social-links[data-astro-cid-upu6fzxr]{display:flex;gap:2rem;justify-content:center}.social-link[data-astro-cid-upu6fzxr]{width:60px;height:60px;display:flex;align-items:center;justify-content:center;background-color:var(--ctp-surface0);border:2px solid var(--social-color);border-radius:50%;color:var(--social-color);transition:all .3s cubic-bezier(.4,0,.2,1);text-decoration:none}.social-icon[data-astro-cid-upu6fzxr]{width:32px;height:32px;display:flex;align-items:center;justify-content:center}.social-icon[data-astro-cid-upu6fzxr] svg{width:100%;height:100%}.social-link[data-astro-cid-upu6fzxr]:hover{background-color:var(--social-color);color:var(--ctp-base);transform:translateY(-4px) rotate(5deg);box-shadow:0 8px 16px #0000004d}.social-link[data-astro-cid-upu6fzxr]:active{transform:translateY(-2px) rotate(5deg)}@media(max-width:600px){.socials[data-astro-cid-upu6fzxr]{padding:3rem 2rem 6rem}.social-links[data-astro-cid-upu6fzxr]{gap:1.5rem}.social-link[data-astro-cid-upu6fzxr]{width:56px;height:56px}.social-icon[data-astro-cid-upu6fzxr]{width:28px;height:28px}}
|
|
||||||
.blog-list[data-astro-cid-35dpazjw]{padding:3rem 2rem;display:flex;justify-content:center}.content[data-astro-cid-35dpazjw]{max-width:800px;width:100%}.posts[data-astro-cid-35dpazjw]{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1.5rem}.post-item[data-astro-cid-35dpazjw]{padding:1.5rem;background-color:var(--ctp-surface0);border:2px solid var(--ctp-lavender);border-radius:12px;transition:transform .2s ease,border-color .2s ease}.post-item[data-astro-cid-35dpazjw]:hover{transform:translateY(-2px);border-color:var(--ctp-pink)}.post-link[data-astro-cid-35dpazjw]{display:flex;flex-direction:column;gap:.4rem;text-decoration:none}.post-title[data-astro-cid-35dpazjw]{font-size:1.3rem;font-weight:600;color:var(--ctp-text)}.post-description[data-astro-cid-35dpazjw]{color:var(--ctp-subtext0);font-size:.95rem}.post-date[data-astro-cid-35dpazjw]{color:var(--ctp-subtext0);font-size:.85rem;opacity:.7}@media(max-width:600px){.blog-list[data-astro-cid-35dpazjw]{padding:2rem 1rem}.post-item[data-astro-cid-35dpazjw]{padding:1rem}}
|
|
||||||
</style></head> <body> <main> <section class="intro" data-astro-cid-u43ozx4m> <div class="content" data-astro-cid-u43ozx4m> <a href="/" data-astro-cid-u43ozx4m> <img src="/_astro/profile-picture.C8jVU_ub.png" alt="trueberryless" class="pfp" data-astro-cid-u43ozx4m> </a> <div class="text" data-astro-cid-u43ozx4m> <h1 data-astro-cid-u43ozx4m>Kristian Emil Takvam</h1> <p class="subtitle" data-astro-cid-u43ozx4m>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lobortis nisi ante, nec tincidunt ligula mattis nec. Maecenas metus mauris, fermentum et vulputate ut, dapibus vel massa.</p> </div> </div> </section> <div class="wave-container" data-astro-cid-5baqliqx> <div class="wave-wrapper" style="--wave-speed: 100s" data-astro-cid-5baqliqx> <svg class="wave wave-slide-left" viewBox="0 0 600 40" preserveAspectRatio="none" data-astro-cid-5baqliqx> <path class="wave-path" d="M 0,20 Q 4,10.399999999999999 8,20 Q 12,29.6 16,20 Q 20,10.399999999999999 24,20 Q 28,29.6 32,20 Q 36,10.399999999999999 40,20 Q 44,29.6 48,20 Q 52,10.399999999999999 56,20 Q 60,29.6 64,20 Q 68,10.399999999999999 72,20 Q 76,29.6 80,20 Q 84,10.399999999999999 88,20 Q 92,29.6 96,20 Q 100,10.399999999999999 104,20 Q 108,29.6 112,20 Q 116,10.399999999999999 120,20 Q 124,29.6 128,20 Q 132,10.399999999999999 136,20 Q 140,29.6 144,20 Q 148,10.399999999999999 152,20 Q 156,29.6 160,20 Q 164,10.399999999999999 168,20 Q 172,29.6 176,20 Q 180,10.399999999999999 184,20 Q 188,29.6 192,20 Q 196,10.399999999999999 200,20 Q 204,29.6 208,20 Q 212,10.399999999999999 216,20 Q 220,29.6 224,20 Q 228,10.399999999999999 232,20 Q 236,29.6 240,20 Q 244,10.399999999999999 248,20 Q 252,29.6 256,20 Q 260,10.399999999999999 264,20 Q 268,29.6 272,20 Q 276,10.399999999999999 280,20 Q 284,29.6 288,20 Q 292,10.399999999999999 296,20 Q 300,29.6 304,20 Q 308,10.399999999999999 312,20 Q 316,29.6 320,20 Q 324,10.399999999999999 328,20 Q 332,29.6 336,20 Q 340,10.399999999999999 344,20 Q 348,29.6 352,20 Q 356,10.399999999999999 360,20 Q 364,29.6 368,20 Q 372,10.399999999999999 376,20 Q 380,29.6 384,20 Q 388,10.399999999999999 392,20 Q 396,29.6 400,20 Q 404,10.399999999999999 408,20 Q 412,29.6 416,20 Q 420,10.399999999999999 424,20 Q 428,29.6 432,20 Q 436,10.399999999999999 440,20 Q 444,29.6 448,20 Q 452,10.399999999999999 456,20 Q 460,29.6 464,20 Q 468,10.399999999999999 472,20 Q 476,29.6 480,20 Q 484,10.399999999999999 488,20 Q 492,29.6 496,20 Q 500,10.399999999999999 504,20 Q 508,29.6 512,20 Q 516,10.399999999999999 520,20 Q 524,29.6 528,20 Q 532,10.399999999999999 536,20 Q 540,29.6 544,20 Q 548,10.399999999999999 552,20 Q 556,29.6 560,20 Q 564,10.399999999999999 568,20 Q 572,29.6 576,20 Q 580,10.399999999999999 584,20 Q 588,29.6 592,20 Q 596,10.399999999999999 600,20 Q 604,29.6 608,20 Q 612,10.399999999999999 616,20 Q 620,29.6 624,20 Q 628,10.399999999999999 632,20 Q 636,29.6 640,20 Q 644,10.399999999999999 648,20 Q 652,29.6 656,20 Q 660,10.399999999999999 664,20 Q 668,29.6 672,20 Q 676,10.399999999999999 680,20 Q 684,29.6 688,20 Q 692,10.399999999999999 696,20 Q 700,29.6 704,20 Q 708,10.399999999999999 712,20 Q 716,29.6 720,20 Q 724,10.399999999999999 728,20 Q 732,29.6 736,20 Q 740,10.399999999999999 744,20 Q 748,29.6 752,20 Q 756,10.399999999999999 760,20 Q 764,29.6 768,20 Q 772,10.399999999999999 776,20 Q 780,29.6 784,20 Q 788,10.399999999999999 792,20 Q 796,29.6 800,20 Q 804,10.399999999999999 808,20 Q 812,29.6 816,20 Q 820,10.399999999999999 824,20 Q 828,29.6 832,20 Q 836,10.399999999999999 840,20 Q 844,29.6 848,20 Q 852,10.399999999999999 856,20 Q 860,29.6 864,20 Q 868,10.399999999999999 872,20 Q 876,29.6 880,20 Q 884,10.399999999999999 888,20 Q 892,29.6 896,20 Q 900,10.399999999999999 904,20 Q 908,29.6 912,20 Q 916,10.399999999999999 920,20 Q 924,29.6 928,20 Q 932,10.399999999999999 936,20 Q 940,29.6 944,20 Q 948,10.399999999999999 952,20 Q 956,29.6 960,20" stroke="var(--ctp-rosewater)" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round" data-astro-cid-5baqliqx></path> </svg> </div> </div> <!--
|
|
||||||
<Posts />
|
|
||||||
<WaveDivider color="teal" speed={100} size={0.8} direction="left" />
|
|
||||||
--> <section class="blog-list" data-astro-cid-35dpazjw> <div class="content" data-astro-cid-35dpazjw> <ul class="posts" data-astro-cid-35dpazjw> </ul> </div> </section> <!-- Use the new component --> <section class="socials" data-astro-cid-upu6fzxr> <div class="content" data-astro-cid-upu6fzxr> <div class="social-links" data-astro-cid-upu6fzxr> <a href="https://github.com/Kremtastic" class="social-link" target="_blank" rel="noopener noreferrer" style="--social-color: var(--ctp-sapphire)" aria-label="GitHub" data-astro-cid-upu6fzxr> <span class="social-icon" data-astro-cid-upu6fzxr><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z"/></svg></span> </a><a href="https://www.linkedin.com/in/kristian-emil-takvam-70a475215" class="social-link" target="_blank" rel="noopener noreferrer" style="--social-color: var(--ctp-blue)" aria-label="LinkedIn" data-astro-cid-upu6fzxr> <span class="social-icon" data-astro-cid-upu6fzxr><svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></span> </a><a href="/rss.xml" class="social-link" target="_blank" rel="noopener noreferrer" style="--social-color: var(--ctp-peach)" aria-label="RSS" data-astro-cid-upu6fzxr> <span class="social-icon" data-astro-cid-upu6fzxr><svg viewBox="0 0 22 24" fill="currentColor"><circle cx="6.18" cy="17.82" r="2.18"/><path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/></svg></span> </a> </div> </div> </section> </main> </body></html>
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "trueberryless",
|
||||||
|
"homepage": "https://github.com/trueberryless-org/trueberryless",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/trueberryless-org/trueberryless/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/trueberryless-org/trueberryless.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "trueberryless <trueberryless@gmail.com> (https://trueberryless.org)",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/netlify": "^6.5.11",
|
||||||
|
"@astrojs/react": "^4.4.0",
|
||||||
|
"@types/react": "^19.1.16",
|
||||||
|
"@types/react-dom": "^19.1.9",
|
||||||
|
"astro-vtbot": "^2.1.9",
|
||||||
|
"astro-webrings": "^0.1.1",
|
||||||
|
"react": "^19.1.1",
|
||||||
|
"react-dom": "^19.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
||||||
|
"astro": "^5.14.5",
|
||||||
|
"prettier": "3.5.3",
|
||||||
|
"prettier-plugin-astro": "0.14.1"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.10.0"
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
|
|
@ -0,0 +1,86 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
const posts = await getCollection("blog");
|
||||||
|
const sortedPosts = posts.sort(
|
||||||
|
(a, b) => b.data.date.getTime() - a.data.date.getTime()
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="blog-list">
|
||||||
|
<div class="content">
|
||||||
|
<ul class="posts">
|
||||||
|
{sortedPosts.map((post) => (
|
||||||
|
<li class="post-item">
|
||||||
|
<a href={`/blog/${post.slug}`} class="post-link">
|
||||||
|
<span class="post-title">{post.data.title}</span>
|
||||||
|
{post.data.description && (
|
||||||
|
<span class="post-description">{post.data.description}</span>
|
||||||
|
)}
|
||||||
|
<span class="post-date">{post.data.date.toDateString()}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.blog-list {
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.posts {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.post-item {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--ctp-lavender);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.post-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--ctp-pink);
|
||||||
|
}
|
||||||
|
.post-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.post-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
}
|
||||||
|
.post-description {
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.post-date {
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.blog-list {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
.post-item {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
---
|
||||||
|
// TODO: List some projects
|
||||||
|
const projects = [
|
||||||
|
{
|
||||||
|
name: "Blog",
|
||||||
|
url: "/blog",
|
||||||
|
color: "var(--ctp-lavender)",
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="project">
|
||||||
|
<div class="content">
|
||||||
|
<h2 class="section-title">Blog</h2>
|
||||||
|
<div class="projects">
|
||||||
|
{
|
||||||
|
projects.map((project) => (
|
||||||
|
<a
|
||||||
|
href={project.url}
|
||||||
|
class="project-link"
|
||||||
|
target=""
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={`--project-color: ${project.color}`}
|
||||||
|
>
|
||||||
|
<span class="project-icon">◈</span>
|
||||||
|
<span class="project-name">{project.name}</span>
|
||||||
|
<svg class="project-arrow" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="M7 17L17 7M17 7H7M17 7V17"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.project {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--project-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -1px;
|
||||||
|
background-color: var(--project-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--project-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-arrow {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: var(--project-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover {
|
||||||
|
transform: translateX(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover .project-icon,
|
||||||
|
.project-link:hover .project-arrow {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover .project-name {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.project {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
---
|
||||||
|
import profilePicture from "../assets/profile-picture.png";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
subtitle: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, subtitle } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="intro">
|
||||||
|
<div class="content">
|
||||||
|
<a href="/">
|
||||||
|
<img src={profilePicture.src} alt="trueberryless" class="pfp" />
|
||||||
|
</a>
|
||||||
|
<div class="text">
|
||||||
|
<h1>{name}</h1>
|
||||||
|
<p class="subtitle">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.intro {
|
||||||
|
padding: 8rem 2rem 6rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pfp {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid var(--ctp-lavender);
|
||||||
|
transition:
|
||||||
|
transform 0.3s ease,
|
||||||
|
border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pfp:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
border-color: var(--ctp-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.intro {
|
||||||
|
padding: 4rem 2rem 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pfp {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
// Get all blog posts from your content collection
|
||||||
|
const posts = await getCollection('blog');
|
||||||
|
// Sort by date if you have a date field in frontmatter
|
||||||
|
const sortedPosts = posts.sort((a, b) =>
|
||||||
|
b.data.date.getTime() - a.data.date.getTime()
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="project">
|
||||||
|
<div class="content">
|
||||||
|
<h2 class="section-title">Blog</h2>
|
||||||
|
<div class="posts">
|
||||||
|
{
|
||||||
|
await Promise.all(sortedPosts.map(async (post) => {
|
||||||
|
const { Content } = await post.render();
|
||||||
|
return (
|
||||||
|
<article class="post">
|
||||||
|
<h3 class="post-title">{post.data.title}</h3>
|
||||||
|
<div class="post-content">
|
||||||
|
<Content />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.project {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--ctp-lavender);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
color: var(--ctp-text);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style markdown content */
|
||||||
|
.post-content :global(p) {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content :global(h1),
|
||||||
|
.post-content :global(h2),
|
||||||
|
.post-content :global(h3) {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--ctp-lavender);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content :global(code) {
|
||||||
|
background-color: var(--ctp-mantle);
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.project {
|
||||||
|
padding: 4rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
---
|
||||||
|
// TODO: List some projects
|
||||||
|
const projects = [
|
||||||
|
{
|
||||||
|
name: "Portal Room",
|
||||||
|
url: "https://github.com/Kremtastic/PortalRoom",
|
||||||
|
color: "var(--ctp-lavender)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Raytracer",
|
||||||
|
url: "https://github.com/Kremtastic/RaytracerUnity",
|
||||||
|
color: "var(--ctp-peach)",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="project">
|
||||||
|
<div class="content">
|
||||||
|
<h2 class="section-title">Projects</h2>
|
||||||
|
<div class="projects">
|
||||||
|
{
|
||||||
|
projects.map((project) => (
|
||||||
|
<a
|
||||||
|
href={project.url}
|
||||||
|
class="project-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={`--project-color: ${project.color}`}
|
||||||
|
>
|
||||||
|
<span class="project-icon">◈</span>
|
||||||
|
<span class="project-name">{project.name}</span>
|
||||||
|
<svg class="project-arrow" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="M7 17L17 7M17 7H7M17 7V17"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.project {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--project-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -1px;
|
||||||
|
background-color: var(--project-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--project-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-arrow {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: var(--project-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover {
|
||||||
|
transform: translateX(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover .project-icon,
|
||||||
|
.project-link:hover .project-arrow {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link:hover .project-name {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.project {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-link {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
---
|
||||||
|
// TODO: Adapt social links
|
||||||
|
const socials = [
|
||||||
|
{
|
||||||
|
name: "GitHub",
|
||||||
|
url: "https://github.com/Kremtastic",
|
||||||
|
color: "var(--ctp-sapphire)",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z"/></svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LinkedIn",
|
||||||
|
url: "https://www.linkedin.com/in/kristian-emil-takvam-70a475215",
|
||||||
|
color: "var(--ctp-blue)",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RSS",
|
||||||
|
url: "/rss.xml",
|
||||||
|
color: "var(--ctp-peach)",
|
||||||
|
icon: `<svg viewBox="0 0 22 24" fill="currentColor"><circle cx="6.18" cy="17.82" r="2.18"/><path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/></svg>`,
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="socials">
|
||||||
|
<div class="content">
|
||||||
|
<div class="social-links">
|
||||||
|
{
|
||||||
|
socials.map((social) => (
|
||||||
|
<a
|
||||||
|
href={social.url}
|
||||||
|
class="social-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={`--social-color: ${social.color}`}
|
||||||
|
aria-label={social.name}
|
||||||
|
>
|
||||||
|
<span class="social-icon" set:html={social.icon} />
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.socials {
|
||||||
|
padding: 4rem 2rem 8rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--social-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--social-color);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon :global(svg) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link:hover {
|
||||||
|
background-color: var(--social-color);
|
||||||
|
color: var(--ctp-base);
|
||||||
|
transform: translateY(-4px) rotate(5deg);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link:active {
|
||||||
|
transform: translateY(-2px) rotate(5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.socials {
|
||||||
|
padding: 3rem 2rem 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-links {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const SoundWave = ({ progress, isPlaying }) => {
|
||||||
|
const [animationTimings, setAnimationTimings] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timings = [];
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
timings.push(Math.floor(Math.random() * 800) + 700);
|
||||||
|
}
|
||||||
|
setAnimationTimings(timings);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const createLines = () => {
|
||||||
|
const progressNum = Math.floor(progress / 2);
|
||||||
|
const lines = [];
|
||||||
|
const y1 = isPlaying ? 3 : 8;
|
||||||
|
const y2 = isPlaying ? 25 : 20;
|
||||||
|
|
||||||
|
for (let i = 1; i < 51; i++) {
|
||||||
|
const isFull = i <= progressNum;
|
||||||
|
lines.push(
|
||||||
|
<line
|
||||||
|
key={i}
|
||||||
|
x1={i + i * 6}
|
||||||
|
y1={y1}
|
||||||
|
x2={i + i * 6}
|
||||||
|
y2={y2}
|
||||||
|
strokeWidth="2"
|
||||||
|
stroke={isFull ? "var(--ctp-green)" : "var(--ctp-overlay0)"}
|
||||||
|
style={{
|
||||||
|
transformOrigin: "center",
|
||||||
|
transition: "stroke 1s ease-in-out",
|
||||||
|
animation: isPlaying
|
||||||
|
? `sound ${animationTimings[i]}ms linear infinite alternate`
|
||||||
|
: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
width="302"
|
||||||
|
height="28"
|
||||||
|
viewBox="0 0 344 28"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style={{ maxWidth: "100%", height: "auto" }}
|
||||||
|
>
|
||||||
|
{createLines()}
|
||||||
|
</svg>
|
||||||
|
<style>{`
|
||||||
|
@keyframes sound {
|
||||||
|
0% {
|
||||||
|
transform: scaleY(0.4);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SoundWave;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
import SpotifyPlayer from "./SpotifyPlayer";
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="spotify">
|
||||||
|
<div class="content">
|
||||||
|
<div class="spotify-container">
|
||||||
|
<SpotifyPlayer client:only="react" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spotify {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotify-container {
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--ctp-green);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.spotify {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotify-container {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import SoundWave from "./SoundWave";
|
||||||
|
|
||||||
|
const SpotifyPlayer = () => {
|
||||||
|
const [track, setTrack] = useState(null);
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchNowPlaying = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/spotify/now-playing");
|
||||||
|
if (response.status === 204) {
|
||||||
|
setTrack(null);
|
||||||
|
setIsPlaying(false);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setTrack(data);
|
||||||
|
setIsPlaying(data.isPlaying);
|
||||||
|
setProgress(data.progress || 0);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching now playing:", error);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchNowPlaying();
|
||||||
|
const interval = setInterval(fetchNowPlaying, 10000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "center", color: "var(--ctp-subtext0)" }}>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "center", color: "var(--ctp-subtext0)" }}>
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
style={{ margin: "0 auto 1rem", color: "var(--ctp-green)" }}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9 18V5l12-2v13M9 18c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3zm12-2c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<p>Not playing anything right now</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
|
||||||
|
<div style={{ display: "flex", gap: "1.5rem", alignItems: "center" }}>
|
||||||
|
{track.albumArt && (
|
||||||
|
<img
|
||||||
|
src={track.albumArt}
|
||||||
|
alt={track.album}
|
||||||
|
style={{
|
||||||
|
width: "80px",
|
||||||
|
height: "80px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "2px solid var(--ctp-green)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.25rem",
|
||||||
|
fontWeight: "600",
|
||||||
|
color: "var(--ctp-text)",
|
||||||
|
marginBottom: "0.25rem",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{track.title}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
color: "var(--ctp-subtext0)",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{track.artist}
|
||||||
|
</div>
|
||||||
|
{track.album && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
color: "var(--ctp-subtext1)",
|
||||||
|
marginTop: "0.25rem",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{track.album}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<SoundWave progress={progress} isPlaying={isPlaying} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{track.trackUrl && (
|
||||||
|
<a
|
||||||
|
href={track.trackUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.5rem",
|
||||||
|
padding: "0.75rem 1.5rem",
|
||||||
|
backgroundColor: "var(--ctp-green)",
|
||||||
|
color: "var(--ctp-base)",
|
||||||
|
borderRadius: "9999px",
|
||||||
|
textDecoration: "none",
|
||||||
|
fontWeight: "500",
|
||||||
|
alignSelf: "center",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.transform = "scale(1.05)";
|
||||||
|
e.currentTarget.style.backgroundColor = "var(--ctp-teal)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.transform = "scale(1)";
|
||||||
|
e.currentTarget.style.backgroundColor = "var(--ctp-green)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open in Spotify
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpotifyPlayer;
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
color: string;
|
||||||
|
speed?: number; // Duration in seconds, default 60
|
||||||
|
size?: number; // Circle size multiplier, default 1
|
||||||
|
direction?: "left" | "right"; // Animation direction, default 'left'
|
||||||
|
}
|
||||||
|
|
||||||
|
const { color, speed = 60, size = 1, direction = "left" } = Astro.props;
|
||||||
|
|
||||||
|
const colorMap: any = {
|
||||||
|
rosewater: "var(--ctp-rosewater)",
|
||||||
|
flamingo: "var(--ctp-flamingo)",
|
||||||
|
pink: "var(--ctp-pink)",
|
||||||
|
mauve: "var(--ctp-mauve)",
|
||||||
|
red: "var(--ctp-red)",
|
||||||
|
maroon: "var(--ctp-maroon)",
|
||||||
|
peach: "var(--ctp-peach)",
|
||||||
|
yellow: "var(--ctp-yellow)",
|
||||||
|
green: "var(--ctp-green)",
|
||||||
|
teal: "var(--ctp-teal)",
|
||||||
|
sky: "var(--ctp-sky)",
|
||||||
|
sapphire: "var(--ctp-sapphire)",
|
||||||
|
blue: "var(--ctp-blue)",
|
||||||
|
lavender: "var(--ctp-lavender)",
|
||||||
|
};
|
||||||
|
|
||||||
|
const waveColor = colorMap[color];
|
||||||
|
|
||||||
|
// Generate smooth wave pattern
|
||||||
|
const generateWavePath = () => {
|
||||||
|
let path = "M 0,20";
|
||||||
|
const circleWidth = 10 * size;
|
||||||
|
const amplitude = 12 * size;
|
||||||
|
const circles = 120;
|
||||||
|
|
||||||
|
for (let i = 0; i < circles; i++) {
|
||||||
|
const x = i * circleWidth;
|
||||||
|
const direction = i % 2 === 0 ? -1 : 1;
|
||||||
|
const y = 20 + amplitude * direction;
|
||||||
|
path += ` Q ${x + circleWidth / 2},${y} ${x + circleWidth},20`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wavePath = generateWavePath();
|
||||||
|
const animationClass =
|
||||||
|
direction === "left" ? "wave-slide-left" : "wave-slide-right";
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="wave-container">
|
||||||
|
<div class="wave-wrapper" style={`--wave-speed: ${speed}s`}>
|
||||||
|
<svg
|
||||||
|
class="wave"
|
||||||
|
class:list={[animationClass]}
|
||||||
|
viewBox="0 0 600 40"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
class="wave-path"
|
||||||
|
d={wavePath}
|
||||||
|
stroke={waveColor}
|
||||||
|
stroke-width="3"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wave-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-wrapper {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
black 8%,
|
||||||
|
black 92%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
-webkit-mask-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
black 8%,
|
||||||
|
black 92%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
width: 300%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-path {
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-slide-left {
|
||||||
|
animation: wave-slide-left var(--wave-speed) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-slide-right {
|
||||||
|
animation: wave-slide-right var(--wave-speed) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave-slide-left {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-33.33%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave-slide-right {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-33.33%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.wave-container {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
---
|
||||||
|
// TODO: Adjust the webrings accoring to https://astro-webrings.lou.gg/ if you want.
|
||||||
|
const webrings = [
|
||||||
|
{
|
||||||
|
name: "Join the Astro webring (shoutouts to Lou)",
|
||||||
|
url: "https://astro-webrings.lou.gg/",
|
||||||
|
color: "var(--ctp-red)",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="webring">
|
||||||
|
<div class="content">
|
||||||
|
<h2 class="section-title">Webrings</h2>
|
||||||
|
<div class="rings">
|
||||||
|
{
|
||||||
|
webrings.map((ring) => (
|
||||||
|
<a
|
||||||
|
href={ring.url}
|
||||||
|
class="ring-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={`--ring-color: ${ring.color}`}
|
||||||
|
>
|
||||||
|
<span class="ring-icon">◈</span>
|
||||||
|
<span class="ring-name">{ring.name}</span>
|
||||||
|
<svg class="ring-arrow" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="M7 17L17 7M17 7H7M17 7V17"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.webring {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
background-color: var(--ctp-surface0);
|
||||||
|
border: 2px solid var(--ring-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -1px;
|
||||||
|
background-color: var(--ring-color);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--ring-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-arrow {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: var(--ring-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link:hover {
|
||||||
|
transform: translateX(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link:hover .ring-icon,
|
||||||
|
.ring-link:hover .ring-arrow {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link:hover .ring-name {
|
||||||
|
color: var(--ctp-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.webring {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-link {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineCollection, z } from 'astro:content';
|
||||||
|
|
||||||
|
const blog = defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
date: z.coerce.date(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { blog };
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="icon" type="image/png" href="/profilePicture.png" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<meta name="darkreader-lock" />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
/* TODO: Adjust the color scheme if you want
|
||||||
|
(recommendation: Catppuccin - https://catppuccin.com/palette/) */
|
||||||
|
:root {
|
||||||
|
/* Catppuccin Mocha */
|
||||||
|
--ctp-rosewater: #f5e0dc;
|
||||||
|
--ctp-flamingo: #f2cdcd;
|
||||||
|
--ctp-pink: #f5c2e7;
|
||||||
|
--ctp-mauve: #cba6f7;
|
||||||
|
--ctp-red: #f38ba8;
|
||||||
|
--ctp-maroon: #eba0ac;
|
||||||
|
--ctp-peach: #fab387;
|
||||||
|
--ctp-yellow: #f9e2af;
|
||||||
|
--ctp-green: #a6e3a1;
|
||||||
|
--ctp-teal: #94e2d5;
|
||||||
|
--ctp-sky: #89dceb;
|
||||||
|
--ctp-sapphire: #74c7ec;
|
||||||
|
--ctp-blue: #89b4fa;
|
||||||
|
--ctp-lavender: #b4befe;
|
||||||
|
--ctp-text: #cdd6f4;
|
||||||
|
--ctp-subtext1: #bac2de;
|
||||||
|
--ctp-subtext0: #a6adc8;
|
||||||
|
--ctp-overlay2: #9399b2;
|
||||||
|
--ctp-overlay1: #7f849c;
|
||||||
|
--ctp-overlay0: #6c7086;
|
||||||
|
--ctp-surface2: #585b70;
|
||||||
|
--ctp-surface1: #45475a;
|
||||||
|
--ctp-surface0: #313244;
|
||||||
|
--ctp-base: #1e1e2e;
|
||||||
|
--ctp-mantle: #181825;
|
||||||
|
--ctp-crust: #11111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--ctp-base);
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="icon" type="image/png" href="/profilePicture.png" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<meta name="darkreader-lock" />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
/* TODO: Adjust the color scheme if you want
|
||||||
|
(recommendation: Catppuccin - https://catppuccin.com/palette/) */
|
||||||
|
:root {
|
||||||
|
/* Catppuccin Mocha */
|
||||||
|
--ctp-rosewater: #f5e0dc;
|
||||||
|
--ctp-flamingo: #f2cdcd;
|
||||||
|
--ctp-pink: #f5c2e7;
|
||||||
|
--ctp-mauve: #cba6f7;
|
||||||
|
--ctp-red: #f38ba8;
|
||||||
|
--ctp-maroon: #eba0ac;
|
||||||
|
--ctp-peach: #fab387;
|
||||||
|
--ctp-yellow: #f9e2af;
|
||||||
|
--ctp-green: #a6e3a1;
|
||||||
|
--ctp-teal: #94e2d5;
|
||||||
|
--ctp-sky: #89dceb;
|
||||||
|
--ctp-sapphire: #74c7ec;
|
||||||
|
--ctp-blue: #89b4fa;
|
||||||
|
--ctp-lavender: #b4befe;
|
||||||
|
--ctp-text: #cdd6f4;
|
||||||
|
--ctp-subtext1: #bac2de;
|
||||||
|
--ctp-subtext0: #a6adc8;
|
||||||
|
--ctp-overlay2: #9399b2;
|
||||||
|
--ctp-overlay1: #7f849c;
|
||||||
|
--ctp-overlay0: #6c7086;
|
||||||
|
--ctp-surface2: #585b70;
|
||||||
|
--ctp-surface1: #45475a;
|
||||||
|
--ctp-surface0: #313244;
|
||||||
|
--ctp-base: #1e1e2e;
|
||||||
|
--ctp-mantle: #181825;
|
||||||
|
--ctp-crust: #11111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--ctp-base);
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// src/pages/api/spotify/now-playing.js
|
||||||
|
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
const CLIENT_ID = import.meta.env.CLIENT_ID;
|
||||||
|
const CLIENT_SECRET = import.meta.env.CLIENT_SECRET;
|
||||||
|
const REFRESH_TOKEN = import.meta.env.REFRESH_TOKEN;
|
||||||
|
|
||||||
|
const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
|
||||||
|
const NOW_PLAYING_ENDPOINT =
|
||||||
|
"https://api.spotify.com/v1/me/player/currently-playing";
|
||||||
|
const TOKEN_ENDPOINT = "https://accounts.spotify.com/api/token";
|
||||||
|
|
||||||
|
const getAccessToken = async () => {
|
||||||
|
const response = await fetch(TOKEN_ENDPOINT, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${basic}`,
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: REFRESH_TOKEN,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNowPlaying = async () => {
|
||||||
|
const { access_token } = await getAccessToken();
|
||||||
|
|
||||||
|
return fetch(NOW_PLAYING_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const response = await getNowPlaying();
|
||||||
|
|
||||||
|
if (response.status === 204 || response.status > 400) {
|
||||||
|
return new Response(null, { status: 204 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const song = await response.json();
|
||||||
|
|
||||||
|
if (!song.item) {
|
||||||
|
return new Response(null, { status: 204 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPlaying = song.is_playing;
|
||||||
|
const title = song.item.name;
|
||||||
|
const artist = song.item.artists.map((_artist) => _artist.name).join(", ");
|
||||||
|
const album = song.item.album.name;
|
||||||
|
const albumArt = song.item.album.images[0]?.url;
|
||||||
|
const trackUrl = song.item.external_urls.spotify;
|
||||||
|
const progress = song.progress_ms
|
||||||
|
? Math.floor((song.progress_ms / song.item.duration_ms) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
isPlaying,
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
albumArt,
|
||||||
|
trackUrl,
|
||||||
|
progress,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=30",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Intro from "../components/Intro.astro";
|
||||||
|
//import Projects from "../components/Projects.astro";
|
||||||
|
import Posts from "../components/Posts.astro";
|
||||||
|
import WaveDivider from "../components/WaveDivider.astro";
|
||||||
|
//import Webring from "../components/Webring.astro";
|
||||||
|
//import SpotifyNowPlaying from "../components/SpotifyNowPlaying.astro";
|
||||||
|
import Socials from "../components/Socials.astro";
|
||||||
|
import BlogList from "../components/BlogList.astro"; // Import the new component
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Fill in your preferences.
|
||||||
|
const variables = {
|
||||||
|
name: "Kristian Emil Takvam",
|
||||||
|
subtitles:
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lobortis nisi ante, nec tincidunt ligula mattis nec. Maecenas metus mauris, fermentum et vulputate ut, dapibus vel massa.",
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={variables.name}>
|
||||||
|
<main>
|
||||||
|
<Intro name={variables.name} subtitle={variables.subtitles} />
|
||||||
|
<WaveDivider color="rosewater" speed={100} size={0.8} direction="left" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<Posts />
|
||||||
|
<WaveDivider color="teal" speed={100} size={0.8} direction="left" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<BlogList /> <!-- Use the new component -->
|
||||||
|
|
||||||
|
|
||||||
|
<Socials />
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
---
|
||||||
|
import { getCollection, type CollectionEntry } from "astro:content";
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
import Intro from "../../components/Intro.astro";
|
||||||
|
import WaveDivider from "../../components/WaveDivider.astro";
|
||||||
|
import Socials from "../../components/Socials.astro";
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getCollection("blog");
|
||||||
|
|
||||||
|
return posts.map((post) => ({
|
||||||
|
params: { slug: post.slug },
|
||||||
|
props: { post },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: CollectionEntry<"blog">;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
const { title, description, date } = post.data;
|
||||||
|
const { Content } = await post.render();
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={title}>
|
||||||
|
<Intro name={title} subtitle={description ?? ""} />
|
||||||
|
<WaveDivider color="lavender" />
|
||||||
|
|
||||||
|
<section class="post">
|
||||||
|
<div class="content">
|
||||||
|
<p class="post-date">{date.toDateString()}</p>
|
||||||
|
<article class="post-body">
|
||||||
|
<Content />
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<WaveDivider color="lavender" direction="right" />
|
||||||
|
<Socials />
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.post {
|
||||||
|
padding: 2rem 2rem 4rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.post-date {
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.post-body {
|
||||||
|
color: var(--ctp-text);
|
||||||
|
line-height: 1.7;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style markdown content, matching Posts.astro */
|
||||||
|
.post-body :global(p) {
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
.post-body :global(h1),
|
||||||
|
.post-body :global(h2),
|
||||||
|
.post-body :global(h3) {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--ctp-lavender);
|
||||||
|
}
|
||||||
|
.post-body :global(a) {
|
||||||
|
color: var(--ctp-pink);
|
||||||
|
}
|
||||||
|
.post-body :global(ul),
|
||||||
|
.post-body :global(ol) {
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.post-body :global(blockquote) {
|
||||||
|
border-left: 3px solid var(--ctp-lavender);
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.post-body :global(code) {
|
||||||
|
background-color: var(--ctp-mantle);
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.post-body :global(pre) {
|
||||||
|
background-color: var(--ctp-mantle);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
.post-body :global(pre code) {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.post-body :global(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.post {
|
||||||
|
padding: 1.5rem 1rem 3rem;
|
||||||
|
}
|
||||||
|
.post-body {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Intro from "../components/Intro.astro";
|
||||||
|
import Projects from "../components/Projects.astro";
|
||||||
|
import Posts from "../components/Posts.astro";
|
||||||
|
import WaveDivider from "../components/WaveDivider.astro";
|
||||||
|
import Webring from "../components/Webring.astro";
|
||||||
|
import SpotifyNowPlaying from "../components/SpotifyNowPlaying.astro";
|
||||||
|
import Socials from "../components/Socials.astro";
|
||||||
|
import Blogs from "../components/Blogs.astro";
|
||||||
|
|
||||||
|
// TODO: Fill in your preferences.
|
||||||
|
const variables = {
|
||||||
|
name: "Kristian Emil Takvam",
|
||||||
|
subtitles:
|
||||||
|
"This is my blog where I share my thoughts on programming, technology, and life in general. Stay tuned for updates!",
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={variables.name}>
|
||||||
|
<main>
|
||||||
|
<Intro name={variables.name} subtitle={variables.subtitles} />
|
||||||
|
<WaveDivider color="rosewater" speed={100} size={0.8} direction="left" />
|
||||||
|
|
||||||
|
<Blogs />
|
||||||
|
<WaveDivider color="teal" speed={100} size={0.8} direction="left" />
|
||||||
|
|
||||||
|
<Projects />
|
||||||
|
<WaveDivider color="flamingo" speed={100} size={0.8} direction="right" />
|
||||||
|
|
||||||
|
<SpotifyNowPlaying />
|
||||||
|
<WaveDivider color="mauve" speed={100} size={0.8} direction="left" />
|
||||||
|
|
||||||
|
<!--<Webring />
|
||||||
|
<WaveDivider color="pink" speed={100} size={0.8} direction="right" />
|
||||||
|
-->
|
||||||
|
<Socials />
|
||||||
|
</main>
|
||||||
|
<footer style="text-align: center;">
|
||||||
|
<p>© 2025 Kristian Emil Takvam | Built with the <a href="https://github.com/trueberryless-org/astro-portfolio-template" target="_blank">Astro-portfolio-template</a> theme by <a href="https://trueberryless.org/" target="_blank">@trueberryless</a></p>
|
||||||
|
</footer>
|
||||||
|
</Layout>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue