惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Last Week in AI
Last Week in AI
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
V
Visual Studio Blog
宝玉的分享
宝玉的分享
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园 - 聂微东
S
SegmentFault 最新的问题
博客园 - 司徒正美
罗磊的独立博客
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
小众软件
小众软件
Jina AI
Jina AI

jb's posts

One year on Nekoweb AI is overpowered now and it's really worrying Redirecting... Does having webrings slow down your site? Things the Indie Web should consider [UPDATE: PATCHED] XSS on Status Cafe: What you need to know jbsite4 is overcomplicated and I love it [RANT] Browser inconsistencies, yes it still exists. Web responsiveness: why you should follow it on your site jb's 17th birthday jbsite4, preparing for takeoff. Nekoweb 1-month Review AIAIAIAIAIAIAIAIAIAI Burnout Making a website doesn't need to cost you money (RANT) I despise annoying scripts The Great Nekoweb Elections My love/hate relationship on Bluetooth JB's site, now hosted on Nekoweb! My First Blog Y2DL Devlog - May 2024
Making this website
2025-03-02 · via jb's posts

#website #jbs-site #making-of

After reading my previous blog post about making a website shouldn’t cost you money, you’re wondering to yourself:

This is broken.

“How did jb make this website?”

So, here’s a post about how did I make my site!

It’s starts with…

So, when starting this website, I picked a framework to use. I picked SvelteKit because:

  • My v2 site (which is scrapped) is already made using SvelteKit,
  • I wanna learn more Svelte so I could use it in the future,
  • and because I just want to.

Then a JS runtime, I decided to go with Bun because that’s the only JS runtime I have installed back then.

Svelte components are great!

It reduces clutter on the file by letting me create components that I can use in the page. It also opens the possibility of using components in multiple pages.

Let’s take a look on one of my components, the now playing widget:

<script context="module">

import { LASTFM_API_KEY, LASTFM_USERNAME } from '../config';

export async function preload() {

try {

const response = await fetch(

`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${LASTFM_USERNAME}&api_key=${LASTFM_API_KEY}&format=json&limit=1`

);

if (!response.ok) {

throw new Error(`HTTP error! status: ${response.status}`);

}

const data = await response.json();

let nowPlaying = null;

if (data.recenttracks && data.recenttracks.track && data.recenttracks.track.length > 0) {

nowPlaying = data.recenttracks.track[0];

if (nowPlaying['@attr'] && nowPlaying['@attr'].nowplaying === 'true') {

nowPlaying.isPlaying = true;

} else {

nowPlaying.isPlaying = false;

}

}

return { nowPlaying };

} catch (error) {

console.error('Error fetching Last.fm data:', error);

return { nowPlaying: null, error: error.message };

}

}

</script>

<script lang="ts">

import autoAnimate from '@formkit/auto-animate';

import { fade } from 'svelte/transition';

export let nowPlaying;

export let error;

let oldNpTitle: string = "";

import { onMount } from 'svelte';

import { writable } from 'svelte/store';

let nowPlayingStore: Writable<any>;

if (typeof window !== 'undefined') {

nowPlayingStore = writable(nowPlaying || null);

} else {

nowPlayingStore = writable(null);

}

async function getNowPlaying() {

try {

const response = await fetch(

`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${LASTFM_USERNAME}&api_key=${LASTFM_API_KEY}&format=json&limit=1`

);

if (!response.ok) {

throw new Error(`HTTP error! status: ${response.status}`);

}

const data = await response.json();

if (data.recenttracks && data.recenttracks.track && data.recenttracks.track.length > 0) {

const newNowPlaying = data.recenttracks.track[0];

if (newNowPlaying['@attr'] && newNowPlaying['@attr'].nowplaying === 'true') {

newNowPlaying.isPlaying = true;

} else {

newNowPlaying.isPlaying = false;

}

if (oldNpTitle != newNowPlaying.name) {

nowPlayingStore.set(null);

setTimeout(function() { nowPlayingStore.set(newNowPlaying); }, 500)

}

oldNpTitle = newNowPlaying.name;

} else {

nowPlayingStore.set(null);

}

} catch (err) {

console.error('Error fetching Last.fm data:', err);

nowPlayingStore.set(null);

}

}

onMount(async () => {

if (typeof window !== 'undefined') {

nowPlayingStore.set(nowPlaying);

if (!nowPlaying) {

await getNowPlaying();

nowPlayingStore.set(nowPlaying);

}

const intervalId = setInterval(getNowPlaying, 20000);

return () => clearInterval(intervalId);

}

});

$: nowPlaying = $nowPlayingStore;

</script>

<div use:autoAnimate>

{#if nowPlaying}

{#key nowPlaying.isPlaying}

<h1 in:fade class=" headercolor mt-[-6px] pb-2 text-2xl">

{#if nowPlaying.isPlaying}

Currently playing

{:else}

Last played

{/if}

</div>

{/key}

<div class="flex items-center justify-center">

<div class="flex-1/4 p-1">

{#key nowPlaying.image}

{#if nowPlaying.image && nowPlaying.image[2] && nowPlaying.image[2]['#text']}

<img

src={nowPlaying.image[2]['#text']}

class="aspect-square "

alt="Album Art"

width="auto"

height="auto"

loading="lazy"

in:fade out:fade

/>

{/if}

{/key}

</div>

<div class="flex-3/4 p-1">

{#key nowPlaying.name}

<a

class="headerfont nocol text-ellipsis"

href={nowPlaying.url}

target="_blank" rel="noopener" in:fade out:fade>

<div class="mt-[-6px] text-ctp-sapphire hover:text-ctp-blue text-3xl sm:text-2xl">

{nowPlaying.name}

</div></a

>

<p class="text-sm" in:fade out:fade>{nowPlaying.artist['#text']}</p>

{#if nowPlaying.album && nowPlaying.album['#text'] && nowPlaying.name !== nowPlaying.album['#text'] && nowPlaying.artist['#text'] !== nowPlaying.album['#text']}

<p class="text-sm" in:fade out:fade>{nowPlaying.album['#text']}</p>

{/if}

{/key}

</div>

</div>

{:else if error}

<p style="color: red;">Error: {error}</p>

{:else}

<p>Loading...</p>

{/if}

</div>

That spaghetti code results to this:

Yes, that above is actually the component itself! We’ll get back to it later.

Helper libraries and stuff

I’ve also extensively used TailwindCSS for styling my site if I can, and if not, I’m reverting to plain old CSS (it’s fine).

For the theme, I use Catppuccin (the Mocha variant) to theme my site.

For deploying my site to Nekoweb , I use svelte-adapter-nekoweb , made by me!

For the transitions between pages and widget transitions, I use AutoAnimate .

I use mostly flexboxes and grids on my layouts to ensure mobile responsiveness.

Blog posts

For blog posts, I make them in svelte-hybrid markdown files using MDSveX , with these unified plugins:

Look at the now playing widget above, because mdsvex processes it as html, it actually works.

For the codeblocks’ syntax highlighting, I use shiki that is integrated with mdsvex.

Tooling is important as the site itself, and here’s what I use:

  • Code editor: Visual Studio Code
  • Browser (for webdev): Firefox (Gecko, main browser), and Chrome (Chromium) (sorry webkit users!)
  • Site’s guestbook: Atabook
  • Music: Apple Music (this isn’t free but you don’t need this)

And that’s about it!

Thanks for coming to see the behind the scenes of my site!