This commit is contained in:
2025-12-26 21:56:11 +08:00
parent e857204140
commit 1688124e8e
31 changed files with 256 additions and 736 deletions

View File

@@ -26,6 +26,9 @@ const bgStyle = computed(() =>
</script> </script>
<template> <template>
<link
rel="stylesheet"
href="https://chinese-fonts-cdn.deno.dev/packages/maple-mono-cn/dist/MapleMono-CN-Regular/result.css" />
<div class="relative"> <div class="relative">
<div <div
v-if="currentBg" v-if="currentBg"

View File

@@ -1,3 +1,15 @@
@import url("https://chinese-fonts-cdn.deno.dev/packages/maple-mono-cn/dist/MapleMono-CN-Regular/result.css");
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@theme {
--font-roboto: "Maple Mono CN", sans-serif;
--font-display--font-feature-settings: "zero", "cv03", "ss03", "ss05", "cv97", "cv98";
}
/* @import url("https://fontsapi.zeoseven.com/292/main/result.css"); */
/* @theme {
--font-roboto: "LXGW WenKai", sans-serif;
} */

View File

@@ -3,10 +3,6 @@ interface Props {
path?: string; path?: string;
title?: string; title?: string;
date?: string; date?: string;
description?: string;
image?: string;
alt?: string;
ogImage?: string;
tags?: Array<string>; tags?: Array<string>;
published?: boolean; published?: boolean;
} }
@@ -15,10 +11,6 @@ withDefaults(defineProps<Props>(), {
path: "/", path: "/",
title: "no-title", title: "no-title",
date: "no-date", date: "no-date",
description: "no-description",
image: "/blogs-img/blog.jpg",
alt: "no-alt",
ogImage: "/blogs-img/blog.jpg",
tags: () => [], tags: () => [],
published: false, published: false,
}); });
@@ -28,21 +20,11 @@ withDefaults(defineProps<Props>(), {
<article <article
class="group border dark:border-gray-800 m-2 rounded-2xl overflow-hidden shadow-sm text-zinc-700 dark:text-zinc-300"> class="group border dark:border-gray-800 m-2 rounded-2xl overflow-hidden shadow-sm text-zinc-700 dark:text-zinc-300">
<NuxtLink :to="path" class="grid grid-cols-1 sm:grid-cols-10 gap-1"> <NuxtLink :to="path" class="grid grid-cols-1 sm:grid-cols-10 gap-1">
<div class="sm:col-span-3"> <div class="sm:col-span-10 p-5">
<NuxtImg
class="h-full w-full object-cover object-center rounded-t-2xl sm:rounded-l-2xl sm:rounded-t-none shadow-lg group-hover:scale-[1.02] transition-all duration-500"
width="300"
:src="image"
:alt="alt" />
</div>
<div class="sm:col-span-7 p-5">
<h2 <h2
class="text-xl font-semibold text-black dark:text-zinc-300 pb-1 group-hover:text-sky-700 dark:group-hover:text-sky-400"> class="text-xl font-semibold text-black dark:text-zinc-300 pb-1 group-hover:text-sky-700 dark:group-hover:text-sky-400">
{{ title }} {{ title }}
</h2> </h2>
<p class="text-ellipsis line-clamp-2">
{{ description }}
</p>
<div class="text-black dark:text-zinc-300 text-sm mt-2 mb-1 md:flex md:space-x-6"> <div class="text-black dark:text-zinc-300 text-sm mt-2 mb-1 md:flex md:space-x-6">
<div class="flex items-center"> <div class="flex items-center">
<LogoDate class="-translate-y-[10%]" /> <LogoDate class="-translate-y-[10%]" />

View File

@@ -45,7 +45,7 @@ withDefaults(defineProps<Props>(), {
</h1> </h1>
<p class="text-lg text-zinc-600 dark:text-zinc-400 max-w-2xl mx-auto leading-relaxed italic"> <p class="text-lg text-zinc-600 dark:text-zinc-400 max-w-2xl mx-auto leading-relaxed italic">
"{{ description }}" {{ description }}
</p> </p>
</div> </div>

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { makeFirstCharUpper } from "@/utils/helper";
const route = useRoute(); const route = useRoute();
// take category from route params & make first char upper // take category from route params & make first char upper
@@ -10,7 +8,7 @@ const category = computed(() => {
if (Array.isArray(name)) strName = name.at(0) || ""; if (Array.isArray(name)) strName = name.at(0) || "";
else strName = name; else strName = name;
return makeFirstCharUpper(strName); return strName;
}); });
</script> </script>

View File

@@ -2,7 +2,7 @@
<div class="flex flex-col dark:text-zinc-300 my-5 md:my-0 md:justify-self-center"> <div class="flex flex-col dark:text-zinc-300 my-5 md:my-0 md:justify-self-center">
<p class="text-black dark:text-zinc-300 text-base font-semibold">Quick Link</p> <p class="text-black dark:text-zinc-300 text-base font-semibold">Quick Link</p>
<NuxtLink to="/" class="hover:underline"> Home </NuxtLink> <NuxtLink to="/" class="hover:underline"> Home </NuxtLink>
<NuxtLink to="/blogs" class="hover:underline"> Blogs </NuxtLink> <NuxtLink to="/archive" class="hover:underline"> Archive </NuxtLink>
<NuxtLink to="/categories" class="hover:underline"> Categories </NuxtLink> <NuxtLink to="/categories" class="hover:underline"> Categories </NuxtLink>
<NuxtLink to="/about" class="hover:underline"> About Me </NuxtLink> <NuxtLink to="/about" class="hover:underline"> About Me </NuxtLink>
</div> </div>

View File

@@ -15,7 +15,7 @@ const techStack = computed(() => {
{ label: "构建平台", value: platform.name, icon: platform.icon }, { label: "构建平台", value: platform.name, icon: platform.icon },
{ label: "图片存储", value: "去图图床", icon: "heroicons:photo" }, { label: "图片存储", value: "去图图床", icon: "heroicons:photo" },
{ label: "软件协议", value: "MIT", icon: "heroicons:document-text" }, { label: "软件协议", value: "MIT", icon: "heroicons:document-text" },
{ label: "文章许可", value: "CC BY-NC-SA 4.0", icon: "heroicons:creative-commons" }, { label: "文章许可", value: "CC BY-NC-SA 4.0", icon: "fa-brands:creative-commons" },
{ {
label: "规范域名", label: "规范域名",
value: siteConfig.siteMeta.url.replace("https://", ""), value: siteConfig.siteMeta.url.replace("https://", ""),

View File

@@ -30,7 +30,7 @@ const siteConfig = {
navbar: { navbar: {
links: [ links: [
// { name: "Home", path: "/", icon: "fa6-solid:house" }, // { name: "Home", path: "/", icon: "fa6-solid:house" },
{ name: "Blogs", path: "/blogs", icon: "fa-solid:newspaper" }, { name: "Archive", path: "/archive", icon: "fa-solid:newspaper" },
{ name: "Categories", path: "/categories", icon: "fa-solid:folder" }, { name: "Categories", path: "/categories", icon: "fa-solid:folder" },
{ name: "Tags", path: "/tags", icon: "fa-solid:tags" }, { name: "Tags", path: "/tags", icon: "fa-solid:tags" },
{ name: "About", path: "/about", icon: "fa-solid:user" }, { name: "About", path: "/about", icon: "fa-solid:user" },

View File

@@ -4,7 +4,7 @@
// export const navItems = [ // export const navItems = [
// { label: "Home", path: "/" }, // { label: "Home", path: "/" },
// { label: "Blogs", path: "/blogs" }, // { label: "Archive", path: "/archive" },
// { label: "Categories", path: "/categories" }, // { label: "Categories", path: "/categories" },
// { label: "About", path: "/about" }, // { label: "About", path: "/about" },
// ]; // ];
@@ -25,8 +25,8 @@ export const footerData = {
// "Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.", // "Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.",
// }; // };
export const blogsPage = { export const archivePage = {
title: "All Blogs", title: "All Archives",
description: "Here you will find all the blog posts I have written & published on this site.", description: "Here you will find all the blog posts I have written & published on this site.",
}; };

View File

@@ -16,10 +16,8 @@ const route = useRoute();
<div <div
class="absolute inset-0 pointer-events-none -z-10 transition-all duration-700 ease-in-out" class="absolute inset-0 pointer-events-none -z-10 transition-all duration-700 ease-in-out"
:style="route.path === '/' ? { clipPath: 'inset(100vh 0 0 0)' } : {}"> :style="route.path === '/' ? { clipPath: 'inset(100vh 0 0 0)' } : {}">
<!-- 基础渐变层 -->
<div <div
class="absolute inset-0 bg-gradient-to-b from-white/70 via-white/50 to-white/80 dark:from-slate-900/80 dark:via-slate-900/70 dark:to-slate-900/90 backdrop-blur-xl"></div> class="absolute inset-0 bg-gradient-to-b from-white/70 via-white/50 to-white/80 dark:from-slate-900/80 dark:via-slate-900/70 dark:to-slate-900/90 backdrop-blur-xl"></div>
<!-- 纹理层增加质感 -->
<div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.05] mix-blend-overlay"></div> <div class="absolute inset-0 opacity-[0.03] dark:opacity-[0.05] mix-blend-overlay"></div>
</div> </div>
</div> </div>

View File

@@ -1,75 +1,24 @@
<template> <script setup>
<h1>Hello</h1> import { ref, onMounted } from "vue";
</template> import { useRoute } from "vue-router";
<!-- <script setup lang="ts"> const content = ref(null);
import { aboutPage, footerData } from "~/data";
useHead({ onMounted(async () => {
title: "About", const route = useRoute();
meta: [
{ const { data: pageData } = await useAsyncData(route.path, () =>
name: "description", queryCollection("content").path(route.path).first(),
content: footerData.aboutAuthor, );
},
], if (pageData.value) {
content.value = pageData.value;
}
}); });
</script> </script>
<template> <template>
<div class="py-5"> <div class="prose prose-lg dark:prose-invert mx-auto px-6 max-w-4xl mt-0 text-left">
<div class="sm:grid grid-cols-8 px-6 py-5 sm:py-9 gap-5 container max-w-5xl mx-auto"> <ContentRenderer v-if="content" :value="content" />
<div class="col-span-5 max-w-md">
<div class="flex justify-between">
<div>
<h1 class="text-xl sm:text-4xl pb-2 font-bold">
{{ aboutPage.title }}
</h1>
<div class="my-3 space-x-2 md:space-x-3 pb-10">
<NuxtLink
:to="socialLinks.githubLink"
target="_blank"
class="px-2 py-1 lg:px-3 lg:py-2 bg-gray-300 text-gray-800 rounded-md dark:bg-slate-700 dark:text-[#F1F2F4]"
aria-label="Github">
<Icon name="fa:github" size="1em" class="-translate-y-[-10%]" />
</NuxtLink>
<NuxtLink
:to="socialLinks.linkedinLink"
target="_blank"
class="px-2 py-1 lg:px-3 lg:py-2 bg-gray-300 text-gray-800 rounded-md dark:bg-slate-700 dark:text-[#F1F2F4]"
aria-label="LinkedIn">
<Icon name="fa:linkedin-square" size="1em" class="-translate-y-[-10%]" />
</NuxtLink>
<NuxtLink
:to="socialLinks.twitterLink"
target="_blank"
class="px-2 py-1 lg:px-3 lg:py-2 bg-gray-300 text-gray-800 rounded-md dark:bg-slate-700 dark:text-[#F1F2F4]"
aria-label="Twitter">
<Icon name="fa:twitter-square" size="1em" class="-translate-y-[-10%]" />
</NuxtLink>
<NuxtLink
:to="socialLinks.stackoverflowLink"
target="_blank"
class="px-2 py-1 lg:px-3 lg:py-2 bg-gray-300 text-gray-800 rounded-md dark:bg-slate-700 dark:text-[#F1F2F4]"
aria-label="StackOverflow">
<Icon name="fa:stack-overflow" size="1em" class="-translate-y-[-10%]" />
</NuxtLink>
</div>
</div>
<div class="sm:hidden block col-span-3 pb-5 dark:text-[#F1F2F4]">
<NuxtImg src="/riyad.jpg" width="125" height="115" quality="50" class="rounded-md" />
</div>
</div>
<h3 class="text-base sm:text-3xl font-semibold pb-7 sm:pb-12">
{{ aboutPage.description }}
</h3>
<p>{{ aboutPage.aboutMe }}</p>
</div>
<div class="hidden sm:block col-span-3">
<NuxtImg src="/riyad.jpg" width="450" height="500" quality="50" class="rounded-md" />
</div>
</div>
</div> </div>
</template> --> </template>

View File

@@ -0,0 +1,48 @@
<script lang="ts" setup>
import type { BlogPost } from "~/types/blog";
const { data } = await useAsyncData("all-archive-post", () => queryCollection("content").all());
const sortedData = computed(() => {
return (
data.value
?.map((articles) => {
const meta = articles.meta as unknown as BlogPost;
return {
path: articles.path,
title: articles.title || "no-title available",
description: articles.description || "no-description available",
image: meta.image || "/not-found.jpg",
alt: meta.alt || "no alter data available",
ogImage: meta.ogImage || "/not-found.jpg",
date: meta.date || "not-date-available",
tags: meta.tags || [],
published: meta.published || false,
};
})
.filter((post) => post.published) // Filter out unpublished posts
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) || [] // Reverse the order
);
});
</script>
<template>
<main class="container max-w-5xl mx-auto text-zinc-600">
<h1 class="text-3xl font-bold my-6">Archive</h1>
<div v-auto-animate class="space-y-5 my-5 px-4">
<template v-for="post in sortedData" :key="post.title">
<ArchiveCard
:path="post.path"
:title="post.title"
:date="post.date"
:description="post.description"
:alt="post.alt"
:tags="post.tags"
:published="post.published" />
</template>
<ArchiveCard v-if="sortedData.length <= 0" title="No Post Found" image="/not-found.jpg" />
</div>
</main>
</template>

View File

@@ -21,6 +21,9 @@ const data = computed<BlogPost>(() => {
date: meta?.date || "not-date-available", date: meta?.date || "not-date-available",
tags: meta?.tags || [], tags: meta?.tags || [],
published: meta?.published || false, published: meta?.published || false,
categories: meta?.categories || [],
meta: meta || {},
path: path || "",
}; };
}); });

View File

@@ -2,14 +2,14 @@
import type { BlogPost } from "@/types/blog"; import type { BlogPost } from "@/types/blog";
const route = useRoute(); const route = useRoute();
// take category from route params & make first char upper // Take category from route params & ensure it's a valid string
const category = computed(() => { const category = computed(() => {
const name = route.params.category || ""; const name = route.params.category || "";
let strName = ""; let strName = "";
if (Array.isArray(name)) strName = name.at(0) || ""; if (Array.isArray(name)) strName = name.at(0) || "";
else strName = name; else strName = name;
return strName; return strName.trim().toLowerCase(); // Convert to lowercase for case-insensitive matching
}); });
const { data } = await useAsyncData(`category-data-${category.value}`, () => const { data } = await useAsyncData(`category-data-${category.value}`, () =>
@@ -18,26 +18,33 @@ const { data } = await useAsyncData(`category-data-${category.value}`, () =>
.then((articles) => .then((articles) =>
articles.filter((article) => { articles.filter((article) => {
const meta = article.meta as unknown as BlogPost; const meta = article.meta as unknown as BlogPost;
return meta.tags.includes(category.value); return (
meta.published &&
meta.categories?.map((cat) => cat.toLowerCase()).includes(category.value)
); // Case-insensitive matching
}), }),
), ),
); );
const formattedData = computed(() => { const formattedData = computed(() => {
return data.value?.map((articles) => { return (
const meta = articles.meta as unknown as BlogPost; data.value
return { ?.map((articles) => {
path: articles.path, const meta = articles.meta as unknown as BlogPost;
title: articles.title || "no-title available", return {
description: articles.description || "no-description available", path: articles.path,
image: meta.image || "/blogs-img/blog.jpg", title: articles.title || "no-title available",
alt: meta.alt || "no alter data available", description: articles.description || "no-description available",
ogImage: meta.ogImage || "/blogs-img/blog.jpg", image: meta.image || "/blogs-img/blog.jpg",
date: meta.date || "not-date-available", alt: meta.alt || "no alter data available",
tags: meta.tags || [], ogImage: meta.ogImage || "/blogs-img/blog.jpg",
published: meta.published || false, date: meta.date || "not-date-available",
}; tags: meta.tags || [],
}); published: meta.published || false,
};
})
.filter((post) => post.published) || [] // Ensure only published posts are shown
);
}); });
useHead({ useHead({
@@ -49,16 +56,6 @@ useHead({
}, },
], ],
}); });
// Generate OG Image
const siteData = useSiteConfig();
defineOgImage({
props: {
title: category.value?.toUpperCase(),
description: `You will find all the ${category.value} related post here`,
siteName: siteData.url,
},
});
</script> </script>
<template> <template>
@@ -77,7 +74,7 @@ defineOgImage({
:og-image="post.ogImage" :og-image="post.ogImage"
:tags="post.tags" :tags="post.tags"
:published="post.published" /> :published="post.published" />
<BlogEmpty v-if="data?.length === 0" /> <BlogEmpty v-if="formattedData.length === 0" />
</div> </div>
</main> </main>
</template> </template>

View File

@@ -1,6 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
import { makeFirstCharUpper } from "@/utils/helper";
const { data } = await useAsyncData("all-blog-post-by-category", () => const { data } = await useAsyncData("all-blog-post-by-category", () =>
queryCollection("content").all(), queryCollection("content").all(),
); );
@@ -8,13 +6,13 @@ const { data } = await useAsyncData("all-blog-post-by-category", () =>
const allTags = new Map(); const allTags = new Map();
data.value?.forEach((blog) => { data.value?.forEach((blog) => {
const tags: Array<string> = (blog.meta.tags as string[]) || []; const categories: Array<string> = (blog.meta.categories as string[]) || [];
tags.forEach((tag) => { categories.forEach((category) => {
if (allTags.has(tag)) { if (allTags.has(category)) {
const cnt = allTags.get(tag); const cnt = allTags.get(category);
allTags.set(tag, cnt + 1); allTags.set(category, cnt + 1);
} else { } else {
allTags.set(tag, 1); allTags.set(category, 1);
} }
}); });
}); });
@@ -46,11 +44,7 @@ defineOgImage({
<main class="container max-w-5xl mx-auto text-zinc-600"> <main class="container max-w-5xl mx-auto text-zinc-600">
<CategoryHero /> <CategoryHero />
<div class="flex flex-wrap px-6 mt-12 gap-3"> <div class="flex flex-wrap px-6 mt-12 gap-3">
<CategoryCard <CategoryCard v-for="topic in allTags" :key="topic[0]" :title="topic[0]" :count="topic[1]" />
v-for="topic in allTags"
:key="topic[0]"
:title="makeFirstCharUpper(topic[0])"
:count="topic[1]" />
</div> </div>
</main> </main>
</template> </template>

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { BlogPost } from "@/types/blog"; import type { BlogPost } from "@/types/blog";
import { makeFirstCharUpper } from "@/utils/helper";
const route = useRoute(); const route = useRoute();
const tag = computed(() => { const tag = computed(() => {
@@ -18,7 +17,7 @@ const { data } = await useAsyncData(`tag-data-${tag.value}`, () =>
.then((articles) => .then((articles) =>
articles.filter((article) => { articles.filter((article) => {
const meta = article.meta as unknown as BlogPost; const meta = article.meta as unknown as BlogPost;
return meta.tags.some((t) => t.toLowerCase() === tag.value.toLowerCase()); return meta.published && meta.tags.some((t) => t.toLowerCase() === tag.value.toLowerCase());
}), }),
), ),
); );
@@ -41,7 +40,7 @@ const formattedData = computed(() => {
}); });
useHead({ useHead({
title: `Tag: ${makeFirstCharUpper(tag.value)}`, title: `Tag: ${tag.value}`,
meta: [ meta: [
{ {
name: "description", name: "description",
@@ -75,10 +74,10 @@ defineOgImage({
</div> </div>
<h1 <h1
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight"> class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
#{{ makeFirstCharUpper(tag) }} #{{ tag }}
</h1> </h1>
<p class="text-zinc-600 dark:text-zinc-400 text-center"> <p class="text-zinc-600 dark:text-zinc-400 text-center">
Found {{ data?.length || 0 }} posts with this tag Found {{ formattedData.length || 0 }} posts with this tag
</p> </p>
</div> </div>
@@ -95,7 +94,7 @@ defineOgImage({
:og-image="post.ogImage" :og-image="post.ogImage"
:tags="post.tags" :tags="post.tags"
:published="post.published" /> :published="post.published" />
<BlogEmpty v-if="data?.length === 0" /> <BlogEmpty v-if="formattedData.length === 0" />
</div> </div>
</main> </main>
</template> </template>

View File

@@ -1,6 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
import { makeFirstCharUpper } from "@/utils/helper";
const { data } = await useAsyncData("all-blog-post-by-tags", () => const { data } = await useAsyncData("all-blog-post-by-tags", () =>
queryCollection("content").all(), queryCollection("content").all(),
); );
@@ -63,7 +61,7 @@ defineOgImage({
class="group relative flex items-center gap-2 px-6 py-3 rounded-2xl bg-white/40 dark:bg-slate-900/40 backdrop-blur-md border border-white/20 dark:border-white/5 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300"> class="group relative flex items-center gap-2 px-6 py-3 rounded-2xl bg-white/40 dark:bg-slate-900/40 backdrop-blur-md border border-white/20 dark:border-white/5 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
<span <span
class="text-lg font-bold text-zinc-700 dark:text-zinc-200 group-hover:text-violet-600 dark:group-hover:text-violet-400 transition-colors"> class="text-lg font-bold text-zinc-700 dark:text-zinc-200 group-hover:text-violet-600 dark:group-hover:text-violet-400 transition-colors">
#{{ makeFirstCharUpper(tag) }} #{{ tag }}
</span> </span>
<span <span
class="flex items-center justify-center min-w-[24px] h-6 px-1.5 rounded-full bg-violet-500/10 text-violet-600 dark:text-violet-400 text-xs font-bold border border-violet-500/20"> class="flex items-center justify-center min-w-[24px] h-6 px-1.5 rounded-full bg-violet-500/10 text-violet-600 dark:text-violet-400 text-xs font-bold border border-violet-500/20">

View File

@@ -6,5 +6,8 @@ export interface BlogPost {
alt: string; alt: string;
ogImage: string; ogImage: string;
tags: string[]; tags: string[];
categories: string[];
published: boolean; published: boolean;
meta: Record<string, unknown>; // 添加 meta 属性
path: string; // 添加 path 属性
} }

View File

@@ -1,6 +1,11 @@
export function makeFirstCharUpper(val: string) { // Utility functions
if (val === "") return val;
const firstChar = val.at(0)?.toLocaleUpperCase() || ""; /**
const otherChar = val.slice(1); * Makes the first character of a string uppercase.
return firstChar + otherChar; * @param str - The string to transform.
* @returns The transformed string.
*/
export function makeFirstCharUpper(str: string): string {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
} }

5
content/about.md Normal file
View File

@@ -0,0 +1,5 @@
# 关于我
一名普通高中生,现居中国天津
# 关于本站

View File

@@ -1,97 +0,0 @@
---
title: How To Connect You Namecheap Domain With Vercel Deployed App
date: 1st Mar 2023
description: Here you will lean how to connect your namecheap domain to vercel deployed app.
image: /blogs-img/blog1.jpg
alt: How To Connect You Namecheap Domain With Vercel Deployed App
ogImage: /blogs-img/blog1.jpg
tags: ["namecheap", "vercel"]
published: true
---
### Introduction
If you've purchased a domain from Namecheap and you want to connect it to your Vercel app, there are a few steps you need to follow. In this blog, we'll guide you through the process of connecting your Namecheap domain with your Vercel app.
### Step 1: Add a custom domain to your Vercel app
The first step is to add your custom domain to your Vercel app. To do this, log in to your Vercel account and go to your app dashboard. Click on "Settings" and then "Domains". Click on "Add Domain" and enter your custom domain name. Then click on "Add".
### Step 2: Get the DNS records from Vercel
Once you've added your custom domain to your Vercel app, you'll need to get the DNS records from Vercel. To do this, go back to the "Domains" section and click on the custom domain you just added. Then click on "DNS Records".
You'll see a list of DNS records that you need to add to your Namecheap account. These include the A record, the CNAME record, and the TXT record.
### Step 3: Add DNS records to Namecheap
Now that you have the DNS records from Vercel, you need to add them to your Namecheap account. To do this, log in to your Namecheap account and go to your domain dashboard. Click on "Advanced DNS" and then "Add New Record".
Add the A record first. In the "Type" dropdown menu, select "A (Address)". In the "Host" field, enter "@" (without the quotes). In the "Value" field, enter the IP address from the Vercel DNS records.
Next, add the CNAME record. In the "Type" dropdown menu, select "CNAME (Alias)". In the "Host" field, enter "www" (without the quotes). In the "Value" field, enter the value from the Vercel DNS records.
Finally, add the TXT record. In the "Type" dropdown menu, select "TXT (Text)". In the "Host" field, enter "@" (without the quotes). In the "Value" field, enter the value from the Vercel DNS records.
### Step 4: Verify DNS records
Once you've added the DNS records to your Namecheap account, you need to verify that they're correct. To do this, go back to your Vercel app dashboard and click on the custom domain. Then click on "Verify DNS Configuration". Vercel will check if the DNS records have been set up correctly.
It may take some time for the DNS records to propagate, so be patient. Once the DNS records have propagated, Vercel will verify them and your custom domain will be connected to your Vercel app.
### Conclusion
`print("1")`
```c
int main() {
printf("1");
}
```
Some references:
* Commit: f8083175fe890cbf14f41d0a06e7aa35d4989587
* Commit (fork): foo@f8083175fe890cbf14f41d0a06e7aa35d4989587
* Commit (repo): remarkjs/remark@e1aa9f6c02de18b9459b7d269712bcb50183ce89
* Issue or PR (`#`): #1
* Issue or PR (`GH-`): GH-1
* Issue or PR (fork): foo#1
* Issue or PR (project): remarkjs/remark#1
* Mention: @wooorm
Some links:
* Commit: <https://github.com/remarkjs/remark/commit/e1aa9f6c02de18b9459b7d269712bcb50183ce89>
* Commit comment: <https://github.com/remarkjs/remark/commit/ac63bc3abacf14cf08ca5e2d8f1f8e88a7b9015c#commitcomment-16372693>
* Issue or PR: <https://github.com/remarkjs/remark/issues/182>
* Issue or PR comment: <https://github.com/remarkjs/remark-github/issues/3#issue-151160339>
* Mention: <https://github.com/ben-eb>
# GFM
## Autolink literals
<www.example.com>, <https://example.com>, and <contact@example.com>.
## Footnote
A note[^1]
[^1]: Big note.
## Strikethrough
~one~ or ~~two~~ tildes.
## Table
| a | b | c | d |
| - | :- | -: | :-: |
## Tasklist
* [ ] to do
* [x] done
Connecting your Namecheap domain to your Vercel app is a relatively simple process. By following the steps outlined in this blog, you'll be able to connect your custom domain in no time. Remember to be patient as it may take some time for the DNS records to propagate. If you run into any issues, don't hesitate to reach out to Vercel support for assistance.

View File

@@ -1,41 +0,0 @@
---
title: How To Fix TailwindCSS Intellisense In Nuxt3 Project
date: 26th Jan 2023
description: In Nuxt3 project tailwind css intellisense doesn't seems to work properly. In this blog I will share a workaround to fix this issue.
image: /blogs-img/blog2.jpg
alt: Hwo to fix tailwind intellisense in nuxt3 project
ogImage: /blogs-img/blog2.jpg
tags: ["nuxt", "tailwindcss"]
published: true
---
### Problems
I had a Nuxt3 and TailwindCSS project. which was opened in VsCode. But the problem was, in my project the tailwind intellisense didn't working properly. I tried to reinstall the vscode tailwind extension but the problem didn't solve properly. Later after doing some research I found a [workaround](https://github.com/tailwindlabs/tailwindcss-intellisense/issues/663#issuecomment-1316788128), That I am sharing here today.
### Why It's Not working
In our nuxt project we have a `.nuxt` directory. Nuxt uses the `.nuxt/` directory in development to generate your Vue application. And if we try to look properly there is also a file called `.nuxt/tailwind.config.cjs`, So tailwind find to config file in the same project, one is in your root directory and another one is in you `.nuxt` directory.
### Possible Workaround
One possible solution is, In your project we call tell the extension to exclude the `.nuxt` directory. To exclude the `.nuxt` directory in your workspace,
- Create a `/.vscode` folder in your project's root level.
- Inside `.vscode` folder add a `settings.json` file
- Copy the below code to `settings.json` file
```json
// /.vscode/settings.json
{
"tailwindCSS.files.exclude": [
"**/.git/**",
"**/node_modules/**",
"**/.hg/**",
"**/.svn/**",
"**/.nuxt/**"
]
}
```
Hopefully now tailwind intellisense start working properly.

View File

@@ -1,36 +0,0 @@
---
title: How To Create Namespace Subdomain & Connect To Vercel App
date: 1st Mar 2023
description: Here we will learn, How To Create Namespace Subdomain & Connect To Vercel App
image: /blogs-img/blog3.jpg
alt: How To Create Namespace Subdomain & Connect To Vercel App
ogImage: /blogs-img/blog3.jpg
tags: ["nuxt", "vercel", "namecheap"]
published: true
---
### Introduction
Creating a subdomain on Namecheap and connecting it with a Vercel deployed app is a straightforward process. In this blog, we will guide you through the steps required to create a subdomain on Namecheap and connect it to your Vercel deployed app.
### Step 1: Create a subdomain on Namecheap
The first step is to create a subdomain on Namecheap. To do this, log in to your Namecheap account and go to your domain dashboard. Click on the "Advanced DNS" tab and then click on "Add New Record".
Select "CNAME (Alias)" from the "Type" dropdown menu. In the "Host" field, enter the name of your subdomain (for example, "app" if you want your subdomain to be "app.yourdomain.com"). In the "Value" field, enter the URL of your Vercel deployed app (for example, "yourapp.vercel.app").
### Step 2: Verify the subdomain
After creating the subdomain, you need to verify that it has been set up correctly. To do this, go to your Vercel deployed app dashboard and click on the "Domains" tab. Click on "Add Domain" and enter the name of your subdomain. Vercel will verify the subdomain and confirm that it has been set up correctly.
### Step 3: Add the subdomain to your Vercel deployed app
Now that your subdomain has been verified, you need to add it to your Vercel deployed app. To do this, go to your app dashboard and click on "Settings". Click on "Domains" and then click on "Add Domain". Enter the name of your subdomain and click on "Add".
### Step 4: Verify the subdomain in Vercel
After adding the subdomain to your Vercel deployed app, you need to verify that it has been set up correctly. To do this, click on the subdomain in your Vercel deployed app dashboard. Click on "Verify DNS Configuration". Vercel will check that the subdomain has been set up correctly and confirm that it is connected to your Vercel deployed app.
### Conclusion
Connecting a subdomain on Namecheap to your Vercel deployed app is a simple process that can be done in a few steps. By following the steps outlined in this blog, you can easily create a subdomain on Namecheap and connect it to your Vercel deployed app. Remember to verify your subdomain in both Namecheap and Vercel to ensure that it has been set up correctly. If you encounter any issues, reach out to Vercel support for assistance.

View File

@@ -1,77 +0,0 @@
---
title: How To Properly Fetch Nuxt Content Data and Render It in Nuxt Pages
date: 1st Mar 2023
description: Here we will learn How To Properly Fetch Nuxt Content Data and Render It in Nuxt Pages
image: /blogs-img/blog4.jpg
alt: How To Properly Fetch Nuxt Content Data and Render It in Nuxt Pages
ogImage: /blogs-img/blog4.jpg
tags: ["nuxt", "nuxt-content"]
published: true
---
### Introduction
Nuxt.js is a popular open-source framework for building Vue.js applications. With the release of Nuxt 3, developers have access to new features and improvements to streamline the development process. One of these features is Nuxt Content v2, which allows you to create and manage content in a simple and efficient way. In this blog post, we will guide you through the steps to connect Nuxt Content v2 with Nuxt 3.
### Step 1: Install the necessary dependencies
The first step is to install the necessary dependencies for Nuxt Content v2. To do this, run the following command:
```js
npm install @nuxt/content@next
```
### Step 2: Configure Nuxt Content v2
Once the dependencies are installed, you need to configure Nuxt Content v2 in your Nuxt 3 project. To do this, create a new file named nuxt.config.js in the root directory of your project and add the following code:
```js
export default {
// Enable Nuxt Content module
modules: [
'@nuxt/content'
],
```
In the above code, we have enabled the Nuxt Content module and set the directory where your content will be stored.
### Step 3: Create content files
Once Nuxt Content v2 is configured, you can create content files in the specified directory. By default, Nuxt Content v2 supports Markdown and YAML file formats. You can create a new file in the `content` directory with the following structure:
```md
---
title: "Hello, world!"
---
# Welcome to Nuxt Content v2
This is an example of a Markdown file created using Nuxt Content v2.
```
In the above code, we have created a Markdown file with a title and a sample content.
### Step 4: Display content on a page
Now that we have created content files, we can display the content on a page. To do this, create a new Vue component in the components directory with the following code:
```vue
<script setup lang="ts">
const { path } = useRoute();
const articles = await queryContent(path).findOne();
</script>
<template>
<main>
<div>
<ContentRenderer :value="articles">
<template #empty>
<p>No content found.</p>
</template>
</ContentRenderer>
</div>
</main>
</template>
```

View File

@@ -1,43 +0,0 @@
---
title: Some Awesome Libraries For Vue3
date: 1st Jan 2023
description: Vue.js is a popular JavaScript framework for building web applications. In this blog post, we will introduce you to some of the awesome libraries for Vue.js in different categories.
image: /blogs-img/blog5.jpg
alt: Some Awesome Libraries For Vue3
ogImage: /blogs-img/blog5.jpg
tags: ["vue", "javascript"]
published: true
---
### Introduction
Vue.js is a popular JavaScript framework for building web applications. It offers a lot of flexibility and ease of use, making it a go-to choice for many developers. One of the advantages of Vue.js is its rich library ecosystem. In this blog post, we will introduce you to some of the awesome libraries for Vue.js in different categories.
### Essential
Some libraries are must have when you are start working with new project, here are my list
- **Vue Router**: Vue Router is the official router for Vue.js. It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze
- **Pinia**: Pinia started as an experiment to redesign what a Store for Vue could look like with the Composition API around November 2019.
- **VueUse**: VueUse is a collection of utility functions based on Composition API. We assume you are already familiar with the basic ideas of Composition API before you continue.
- **Vitest**: Vitest is a blazing fast unit test framework powered by Vite.
- **Vue Macro**: Vue Macros is a library that implements proposals or ideas that have not been officially implemented by Vue. That means it will explore and extend more features and syntax sugar to Vue.
### UI Libraries
UI libraries provide pre-built components and styles for building user interfaces. Here are some popular UI libraries for Vue.js:
- **Naive UI**: A Vue 3 Component Library Fairly Complete, Theme Customizable, Uses TypeScript, Fast Kinda Interesting
- **Vuetify**: Vue Component Framework Vuetify is a no design skills required UI Library with beautifully handcrafted Vue Components.
- **Vuestic**: You can create a new project or integrate Vuestic UI into an existing application. There are three ways to create new Vuestic App. All of them mostly the same and provides the same features.
### Others
- **VueFire**: VueFire Official Firebase bindings for Vue.js
- **Vue I118**: Vue I18n Internationalization plugin for Vue.js
- **Vue Auto Animate**: Add motion to your apps with a single line of code.
- **Vuelidate**: Vuelidate is considered model-based because the validation rules are defined next to your data, and the validation tree structure matches the data model structure.
### Conclusion
In this blog post, we have introduced you to some of the awesome libraries for Vue.js in different categories. These libraries can help you build better and more engaging web applications with Vue.js. Remember to choose the right library based on your project requirements and always refer to the documentation for usage and integration instructions. If you encounter any issues, reach out to the Vue.js community for assistance.

View File

@@ -1,30 +0,0 @@
---
title: How to fix vuex type issue
date: 9th June 2024
description: In recent vue project we see that vuex type not working properly. We will fix that type issue and make vuex type workable
image: /blogs-img/blog6.jpg
alt: How to fix vuex type issue
ogImage: /blogs-img/blog6.jpg
tags: ["vue", "vuex"]
published: true
---
### Introduction
In recent version of our vue project, when we try to add vuex we see type error and vuex type not found. We can easily fix that issue.
### How to fix that issue
1. Create a `vuex.d.ts` file inside of your route project.
2. Pase this code in that file
```ts
declare module "vuex" {
export * from "vuex/types/index.d.ts";
export * from "vuex/types/helpers.d.ts";
export * from "vuex/types/logger.d.ts";
export * from "vuex/types/vue.d.ts";
}
```
3. That's it. Your are ok to go.

View File

@@ -1,108 +0,0 @@
---
title: Redis TTL, Jitter, and How I Almost Crashed a Server 🚀
date: 18th Sep 2025
description: Recently, I ran into an interesting Redis case that taught me a big lesson Infinite cache TTLs are like hoarding—things pile up until its a problem.
image: /blogs-img/blog7.png
alt: Redis TTL, Jitter, and How I Almost Crashed a Server 🚀
ogImage: /blogs-img/blog7.png
tags: ["redis", "ttl", "jitter"]
published: true
---
### Redis TTL, Jitter, and How I Almost Crashed a Server 🚀
Recently, I ran into an interesting Redis case that taught me a big lesson:
**Infinite cache TTLs are like hoarding—things pile up until its a problem.**
---
### The Setup: Infinite Cache
Once upon a time (okay, just a few months ago), we were saving some data in Redis with **no expiration**. The idea was simple:
- Data comes from another system (the _real_ source of truth).
- We cache it in Redis for fast access.
- Done. Easy. ✅
But heres the problem: when you never expire cache, it **keeps growing**. And growing. And growing. Like that drawer in your house where you throw every cable youve ever owned.
### The Task: Add a TTL
One day, I got the task:
> “Please set a TTL of two weeks for this cache.”
Sounds easy, right? Just add:
```js
redis.set("mykey", value, "EX", 1209600); // 2 weeks in seconds
```
Boom. Done. Task finished. Go get coffee. ☕
Except… not really.
### The Problem: Cache Avalanche
Think about what happens **two weeks later**.
Every single cached key expires **at the same time**.
Suddenly, Redis says:
> “Sorry boss, no cache here!”
And then our poor backend server (the real source of truth) gets flooded with requests, like:
```
HELP! SEND DATA! SEND DATA! SEND DATA!
```
The server could literally crash under the unexpected load. This is called a **cache avalanche**.
### The Solution: Add Jitter
The trick is simple but powerful: **dont let all keys expire at once.**
Instead of setting **exactly 2 weeks**, we add a little randomness (aka _jitter_). For example:
```js
// Expire between 14 and 16 days
const baseTTL = 14 * 24 * 60 * 60; // 14 days
const jitter = Math.floor(Math.random() * (2 * 24 * 60 * 60)); // up to 2 days
const ttl = baseTTL + jitter;
redis.set("mykey", value, "EX", ttl);
```
Now some keys expire in **14 days**, some in **15**, some in **16**.
Which means requests trickle back to the server instead of hitting it like a tsunami. 🌊
### Why It Matters
Without jitter:
- Day 14 → server gets **millions of requests at once**. Boom. 🔥
With jitter:
- Day 14 → some requests
- Day 15 → some more
- Day 16 → a few more
- Server is chill. 😎
This small change can **save your entire system** from crashing.
### Final Thoughts
Caching is powerful, but it comes with hidden gotchas.
- Infinite TTL? Your cache becomes a junkyard.
- Fixed TTL? Your server might collapse in 14 days like a time bomb.
- TTL with jitter? Balanced, safe, and production-ready.
So the next time you set a cache TTL, remember:
👉 _Always sprinkle some randomness in your Redis life._
Your future self (and your backend servers) will thank you. 🙏
Do you want me to also add a **diagram (ASCII or image idea)** showing the difference between _no jitter vs jitter_ so its more visually clear for the blog?

View File

@@ -1,149 +0,0 @@
---
title: FLOAT Made My Dollars Float Away - FLOAT vs DECIMAL in MySQL 💸
date: 19th Sep 2025
description: Recently I got a task Alter a table column from `FLOAT` to `DECIMAL(10,2)
image: /blogs-img/blog8.png
alt: FLOAT Made My Dollars Float Away - FLOAT vs DECIMAL in MySQL 💸
ogImage: /blogs-img/blog8.png
tags: ["mysql", "float", "decimal"]
published: true
---
### FLOAT Made My Dollars Float Away - FLOAT vs DECIMAL in MySQL
Recently I got a task:
> **"Alter a table column from `FLOAT` to `DECIMAL(10,2)`"**
I thought:
_"Pff, easy task. Just run an `ALTER TABLE` and done. Why is this even a ticket?"_
But then I read the description.
Turns out, **FLOAT was causing data loss**, and I needed to convert it without losing data.
Thats when I realized: this isnt just about one query. Its about how **FLOAT silently eats your money** in MySQL.
### Why FLOAT is a Problem
`FLOAT` in MySQL is a **binary floating-point type**.
It doesnt store exact values — only approximations.
Thats fine for rocket science 🚀 or graphics rendering 🎮, but for **money** where every cent matters? Disaster.
Think of `FLOAT` as a leaky bucket. You pour in `$1,000,000.25`… and it gives you back `$999,999.94`.
Not funny when its your salary.
### Example of Data Loss
```sql
CREATE TABLE money_float (
id INT AUTO_INCREMENT PRIMARY KEY,
amount FLOAT
);
INSERT INTO money_float (amount) VALUES (1000000.25), (123456789.99);
SELECT * FROM money_float;
```
Result:
| id | amount |
| --- | ------------ |
| 1 | 1000000.25 |
| 2 | 123456792.00 |
We inserted `123456789.99`, but got back `123456792.00`.
The bigger the number, the worse the corruption.
### But Why Does FLOAT Lose Data?
Heres the fun part. Lets make it simple.
- `FLOAT` stores numbers in **binary (base 2)**.
- But not every decimal number can be written exactly in binary.
Example:
- In decimal, `0.1` is simple.
- In binary, `0.1` is **infinite repeating**: `0.0001100110011…`
- So `FLOAT` cuts it off at some point and stores an approximation.
Thats why when you do:
```sql
INSERT INTO money_float (amount) VALUES (0.1);
SELECT amount FROM money_float;
```
You might see something like `0.10000000149`.
Now imagine this tiny error repeated in **millions of dollars**.
Errors pile up, and suddenly your 9-digit amount looks… off.
### DECIMAL to the Rescue
`DECIMAL` stores numbers differently:
- Instead of binary approximation, it stores **exact digits as strings** internally.
- That means `123456789.99` is stored as exactly `123456789.99`.
```sql
CREATE TABLE money_decimal (
id INT AUTO_INCREMENT PRIMARY KEY,
amount DECIMAL(15,2)
);
INSERT INTO money_decimal (amount) VALUES (1000000.25), (123456789.99);
SELECT * FROM money_decimal;
```
Result:
| id | amount |
| --- | ------------ |
| 1 | 1000000.25 |
| 2 | 123456789.99 |
Perfect. ✅ No rounding surprises.
### Why ALTER Wont Save You
Heres the trap I fell into:
```sql
ALTER TABLE money_float MODIFY amount DECIMAL(15,2);
```
Youd think this fixes it, right?
Nope. ❌
The data was already corrupted when it was first inserted as `FLOAT`.
`ALTER` just moves the already-broken value into `DECIMAL`.
Garbage in → garbage out.
### Visual: FLOAT vs DECIMAL
```
FLOAT (approximation in binary):
123456789.99 ---> 123456792.00 💀
DECIMAL (exact digits):
123456789.99 ---> 123456789.99 ✅
```
### Lessons Learned
- Never use `FLOAT`/`DOUBLE` for money.
- Always use `DECIMAL(precision, scale)` (e.g., `DECIMAL(15,2)`).
- If your table already has money in `FLOAT`, you cannot fix the lost precision with `ALTER`. Youll need to re-import or clean it at the source.
### Final Thought
Using `FLOAT` for money is like paying your salary in **Monopoly money**. 🎲💵
It looks okay until you try to spend it — then you realize its worthless.
Stick with `DECIMAL`, and your dollars will stay safe. ✅

View File

@@ -0,0 +1,82 @@
---
title: "在Windows下配置Fish"
published: true
date: 2025-10-03
updatedDate: 2025-10-03
description: "在Windows下配置Fish"
image: https://img.rhen.cloud/file/Blog/1761401028478_PixPin_2025-10-03_15-44-52.png
alt: "在Windows下配置Fish"
tags: ["Development", "Windows", "Fish"]
categories: ["Technology"]
---
## 前言
在经历了 Linux 下一系列的生态折磨后,我最终回归了 Windows 的怀抱。
但是 Powershell 实在是太难用了。所以Fish启动
## 安装 Fish
依据[Fish 官网](https://fishshell.com/),我们有三种方式在 Windows 上安装 Fish
- 通过 Cygwin 安装
- 通过 MSYS2 安装
- 通过 WSL 安装
~~作为一个Arch用户肯定是秒选用pacman做包管理的MSYS2。~~
### 安装MSYS2
```bash
scoop install msys2
```
安装完后会提示`Please run 'msys2' now for the MSYS2 setup to complete!`
依照提示运行`msys2`
这样就成功进入MSYS2的环境了。
![PixPin_2025-10-03_15-29-07.png](https://img.rhen.cloud/file/Blog/1761401002178_PixPin_2025-10-03_15-29-07.png)
### 配置MSYS2
#### 更换软件源
在MSYS2环境下运行
```bash
sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist*
pacman -Syy
```
#### 更改home 目录
修改`/etc/nsswitch.conf`文档
```bash
# Begin /etc/nsswitch.conf
passwd: files db
group: files db
db_enum: cache builtin
# db_home: cygwin desc # 修改此行
db_home: /c/Users/%U
db_shell: cygwin desc
db_gecos: cygwin desc
# End /etc/nsswitch.conf
```
### 安装Fish
```bash
pacman -S fish
```
完成可以愉快的使用Fish了
![PixPin_2025-10-03_15-44-52.png](https://img.rhen.cloud/file/Blog/1761401028478_PixPin_2025-10-03_15-44-52.png)

View File

@@ -1,8 +1,12 @@
import siteConfig from "./app/config"; import siteConfig from "./app/config";
// import tailwindcss from "@tailwindcss/vite";
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: "2025-12-20", compatibilityDate: "2025-12-20",
srcDir: "app", srcDir: "app",
css: ["~/assets/css/tailwind.css"],
// plugins: [tailwindcss()],
modules: [ modules: [
"@nuxt/icon", "@nuxt/icon",
@@ -51,6 +55,8 @@ export default defineNuxtConfig({
}, },
}, },
// fonts: {},
colorMode: { colorMode: {
classSuffix: "", classSuffix: "",
preference: "dark", preference: "dark",

View File

@@ -12,7 +12,26 @@ module.exports = {
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
spacegrotesk: ["Space Grotesk", "sans-serif"], // spacegrotesk: ["Space Grotesk", "sans-serif"],
custom: ["Inter"],
sans: [
"Maple Mono CN",
"Inter",
"system-ui",
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"Roboto",
"Helvetica Neue",
"Arial",
"Noto Sans",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
mono: ["Maple Mono CN", "Fira Code", "monospace"],
}, },
}, },
}, },