Repository: fayazara/zooper
Branch: main
Commit: 64b04ddc44ff
Files: 96
Total size: 67.9 KB
Directory structure:
gitextract_pxr4h3_l/
├── .gitignore
├── .npmrc
├── .prettierrc
├── .vscode/
│ └── settings.json
├── README.md
├── app.config.ts
├── app.vue
├── components/
│ ├── App/
│ │ ├── ArticleCard.vue
│ │ ├── Footer.vue
│ │ ├── Header.vue
│ │ ├── Navbar.vue
│ │ ├── ProjectCard.vue
│ │ ├── ThemeToggle.vue
│ │ ├── UsesHeader.vue
│ │ └── UsesItem.vue
│ ├── Home/
│ │ ├── Divider.vue
│ │ ├── FeaturedArticles.vue
│ │ ├── FeaturedProjects.vue
│ │ ├── Intro.vue
│ │ ├── Newsletter.vue
│ │ └── SocialLinks.vue
│ └── content/
│ ├── AnimatedCounter.vue
│ ├── CodeView.vue
│ ├── Credit.vue
│ ├── Encryption.vue
│ ├── HackerButton.vue
│ ├── LabCard.vue
│ ├── Rocket.vue
│ ├── Shapes.vue
│ └── TextRotator.vue
├── content/
│ ├── articles/
│ │ ├── building-your-first-api-with-expressjs-a-beginners-guide.md
│ │ └── how-to-convert-a-svg-to-png-using-canvas.md
│ ├── lab/
│ │ ├── 1.text-rotator.md
│ │ ├── 2.hacker-button.md
│ │ ├── 3.animated-number-counter.md
│ │ ├── 4.shapes-with-tailwindcss.md
│ │ ├── 5.rocket.md
│ │ └── 6.encryption.md
│ ├── projects/
│ │ ├── 1.feedbackjar.json
│ │ ├── 10.fluenticons.json
│ │ ├── 11.appydev.json
│ │ ├── 12.gitstars.json
│ │ ├── 13.tvflix.json
│ │ ├── 2.feedful.json
│ │ ├── 3.1.hawa.json
│ │ ├── 3.123.sketch-to-ui.json
│ │ ├── 3.2.pocketbase-nuxt.json
│ │ ├── 3.formdata.json
│ │ ├── 4.simpleonlinetools.json
│ │ ├── 5.imbox.json
│ │ ├── 6.bring-back-twitter.json
│ │ ├── 6.onelink.json
│ │ ├── 7.logspot.json
│ │ ├── 8.iconbuddy.json
│ │ └── 9.postperfect.json
│ └── uses/
│ ├── Tableplus.json
│ ├── apple-airpods-3.json
│ ├── apple-iphone-12.json
│ ├── apple-watch-series-7.json
│ ├── bear.json
│ ├── cleanshot.json
│ ├── daily-objects-turf-desk-mat.json
│ ├── dell-nero-dock.json
│ ├── fake-plants.json
│ ├── featherlite-helix-chair.json
│ ├── gifski.json
│ ├── httpie.json
│ ├── ikea-headphone-stand.json
│ ├── jbl-csum-microphone.json
│ ├── keychron-k2.json
│ ├── krisp.json
│ ├── lenovo-monitor.json
│ ├── logitech-mx-keys.json
│ ├── logitech-mx-master-3s.json
│ ├── macbook.json
│ ├── miyoo-mini.json
│ ├── monitorcontrol.json
│ ├── purple-ark-sit-stand-desk.json
│ ├── raycast.json
│ ├── reminders-menubar.json
│ ├── sony-playstation-4.json
│ ├── sony-wh-1000xm4.json
│ ├── texts.json
│ ├── vscode.json
│ └── zed.json
├── nuxt.config.ts
├── package.json
├── pages/
│ ├── articles/
│ │ ├── [slug].vue
│ │ └── index.vue
│ ├── bookmarks.vue
│ ├── index.vue
│ ├── lab.vue
│ ├── projects.vue
│ └── whats-in-my-bag.vue
├── tailwind.config.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example
================================================
FILE: .npmrc
================================================
shamefully-hoist=true
strict-peer-dependencies=false
================================================
FILE: .prettierrc
================================================
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"printWidth": 80
}
================================================
FILE: .vscode/settings.json
================================================
{
"files.associations": {
"*.css": "tailwindcss"
},
"editor.quickSuggestions": {
"strings": true
},
"tailwindCSS.experimental.configFile": "tailwind.config.ts",
"tailwindCSS.experimental.classRegex": [
[
"ui:\\s*{([^)]*)\\s*}",
"[\"'`]([^\"'`]*).*?[\"'`]"
],
[
"/\\*ui\\*/\\s*{([^;]*)}",
":\\s*[\"'`]([^\"'`]*).*?[\"'`]"
]
],
"tailwindCSS.classAttributes": [
"class",
"className",
"ngClass",
"ui"
]
}
================================================
FILE: README.md
================================================
[<img src="https://github.com/user-attachments/assets/60e89805-26fd-4074-8ced-447fb148c7e6">](http://supersaas.dev?ref=github)
# Zooper
Zooper is a beautiful personal portfolio template for developers, programmers, freelancers and designers.

## Features
Full features blog 📝.
CMS ready 🚀.
Super fast ⚡.
SEO friendly 📈.
Fully responsive 📱.
Dark mode 🌗.
Syntax highlighting 🌈.
Social media links 🔗.
Code preview playground 🎮.
Uses page 🧑💻
Bookmarks 📑.
## Roadmap
Comments
Tags
Categories
Pagination
Sitemap
RSS feeds
Search
Likes
## Tech Stack
1. Nuxt JS
2. Tailwind CSS
3. Vue
4. Nuxt Content Module
5. Shiki JS ES
## Installation
1. `git clone` this repo or click on `Use this template` button.
2. `cd` into the project directory.
3. Run `yarn install` to install the dependencies.
4. Run `yarn dev` to start the development server.
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
================================================
FILE: app.config.ts
================================================
export default defineAppConfig({
ui: {
primary: "teal",
gray: "neutral",
formGroup: {
help: "text-xs mt-1 text-gray-500 dark:text-gray-400",
error: "text-xs mt-1 text-red-500 dark:text-red-400",
label: {
base: "text-sm block font-medium text-gray-500 dark:text-gray-200",
},
},
button: {
rounded:
"rounded-md transition-transform active:scale-x-[0.98] active:scale-y-[0.99]",
},
modal: {
overlay: {
background: "bg-[rgba(0,8,47,.275)] saturate-50",
},
padding: "p-0",
rounded: "rounded-t-2xl sm:rounded-xl",
transition: {
enterFrom: "opacity-0 translate-y-full sm:translate-y-0 sm:scale-x-95",
leaveFrom: "opacity-100 translate-y-0 sm:scale-x-100",
},
},
container: {
constrained: "max-w-2xl",
},
},
});
================================================
FILE: app.vue
================================================
<template>
<NuxtLoadingIndicator color="#14b8a6" />
<AppNavbar />
<div class="h-32"></div>
<UContainer>
<NuxtPage />
</UContainer>
<div class="h-32"></div>
<AppFooter />
</template>
<style>
.page-enter-active,
.page-leave-active {
transition: all 0.2s;
}
.page-leave-to {
opacity: 0;
transform: translateY(-5px);
}
.page-enter-from {
opacity: 0;
transform: translateY(5px);
}
</style>
================================================
FILE: components/App/ArticleCard.vue
================================================
<template>
<NuxtLink :to="article._path" class="group">
<article>
<time
class="relative z-10 order-first mb-3 flex items-center text-sm text-gray-400 dark:text-gray-500 pl-3.5"
datetime="2022-09-05"
><span
class="absolute inset-y-0 left-0 flex items-center"
aria-hidden="true"
><span
class="h-4 w-0.5 rounded-full bg-gray-200 dark:bg-gray-500"
></span
></span>
{{ getReadableDate(article.published) }}
</time>
<h2
class="text-base font-semibold font-display tracking-tight text-gray-800 dark:text-gray-100 group-hover:text-primary-600"
>
{{ article.title }}
</h2>
<p class="relative z-10 mt-2 text-sm text-gray-600 dark:text-gray-400">
{{ article.description }}
</p>
</article>
</NuxtLink>
</template>
<script setup>
defineProps({
article: {
type: Object,
required: true,
},
});
const getReadableDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
};
</script>
================================================
FILE: components/App/Footer.vue
================================================
<template>
<footer
class="max-w-2xl mx-auto text-gray-400 dark:text-gray-600 text-sm text-center pb-8"
>
<br>
<p>© 2023 Fayaz Ahmed. All rights reserved.</p>
</footer>
</template>
================================================
FILE: components/App/Header.vue
================================================
<template>
<div>
<h1
class="text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100"
>
{{ title }}
</h1>
<p class="mt-6 text-base text-gray-600 dark:text-gray-400">
{{ description }}
</p>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
});
</script>
================================================
FILE: components/App/Navbar.vue
================================================
<template>
<div ref="headerRef" :style="styles" class="fixed top-0 w-full z-50">
<nav class="mx-auto px-4 sm:px-6 lg:px-8 max-w-2xl">
<ul
class="flex items-center my-4 px-3 text-sm font-medium text-gray-800 rounded-full shadow-lg bg-white/90 shadow-gray-800/5 ring-1 backdrop-blur dark:bg-gray-800/90 dark:text-gray-200 dark:ring-white/20 ring-gray-900/5"
>
<li v-for="item in items" :key="item.path">
<UTooltip
:text="item.name"
:ui="{ popper: { strategy: 'absolute' } }"
>
<ULink
:to="item.path"
class="relative px-3 py-4 flex items-center justify-center transition hover:text-primary-500 dark:hover:text-primary-400"
active-class="text-primary-600 dark:text-primary-400"
>
<Icon aria-hidden="true" :name="item.icon" class="w-5 h-5 z-10" />
<span
v-if="$route.path === item.path"
class="absolute inset-x-1 -bottom-px h-px bg-gradient-to-r from-primary-500/0 via-primary-500/70 to-primary-500/0 dark:from-primary-400/0 dark:via-primary-400/40 dark:to-primary-400/0"
></span>
<span
v-if="$route.path === item.path"
class="absolute h-8 w-8 z-0 rounded-full bg-gray-100 dark:bg-white/10 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
></span>
<span class="sr-only">{{ item.name }}</span>
</ULink>
</UTooltip>
</li>
<li class="flex-1"></li>
<li>
<AppThemeToggle />
</li>
</ul>
</nav>
</div>
</template>
<script setup>
import { useFixedHeader } from 'vue-use-fixed-header'
const headerRef = ref(null);
const { styles } = useFixedHeader(headerRef);
const items = [
{ name: "Home", path: "/", icon: "solar:home-smile-outline" },
{
name: "Projects",
path: "/projects",
icon: "solar:folder-with-files-outline",
},
{
name: "Articles",
path: "/articles",
icon: "solar:document-add-outline",
},
{ name: "Lab", path: "/lab", icon: "heroicons:beaker" },
{
name: "What's in my bag?",
path: "/whats-in-my-bag",
icon: "solar:backpack-outline",
},
{
name: "Bookmarks",
path: "/bookmarks",
icon: "solar:bookmark-linear",
},
];
</script>
================================================
FILE: components/App/ProjectCard.vue
================================================
<template>
<NuxtLink
class="flex items-end gap-4 group p-2 -m-2 rounded-lg"
:to="project.url"
target="_blank"
external
>
<div class="max-w-sm">
<h3 class="text-sm font-medium group-hover:text-primary-600">
{{ project.name }}
</h3>
<p class="text-gray-400 text-sm">{{ project.description }}</p>
</div>
<div
class="flex-1 border-b border-dashed border-gray-300 dark:border-gray-800 group-hover:border-gray-700"
></div>
<UAvatar
:src="project.thumbnail"
:ui="{ rounded: 'rounded z-10 relative' }"
size="md"
:alt="project.name"
/>
</NuxtLink>
</template>
<script setup>
defineProps({
project: {
type: Object,
required: true,
},
});
</script>
================================================
FILE: components/App/ThemeToggle.vue
================================================
<script setup>
const colorMode = useColorMode();
const isDark = computed({
get() {
return colorMode.value === "dark";
},
set() {
colorMode.preference = colorMode.value === "dark" ? "light" : "dark";
},
});
</script>
<template>
<UTooltip text="Toggle theme" :ui="{ popper: { strategy: 'absolute' } }">
<button
class="relative px-3 py-4 flex items-center justify-center transition hover:text-primary-500 dark:hover:text-primary-400"
@click="isDark = !isDark"
>
<Icon
aria-hidden="true"
:name="isDark ? 'solar:sun-2-outline' : 'solar:moon-outline'"
class="w-5 h-5"
/>
<span class="sr-only">Toggle theme</span>
</button>
</UTooltip>
</template>
================================================
FILE: components/App/UsesHeader.vue
================================================
<template>
<li>
<div
class="relative after:-z-10 after:block after:h-[2px] after:absolute after:top-1/2 after:transform after:bg-gray-100 dark:after:bg-white/10 after:w-full after:left-0 after:right-0"
>
<span
class="font-medium text-sm text-gray-600 dark:text-gray-500 bg-gray-50 dark:bg-black pr-4"
>{{ title }}</span
>
</div>
</li>
</template>
<script lang="ts" setup>
defineProps({
title: String,
});
</script>
================================================
FILE: components/App/UsesItem.vue
================================================
<template>
<li>
<NuxtLink :to="item.url" class="group" target="_blank" external>
<p
class="text-base font-semibold text-gray-700 dark:text-gray-300 group-hover:text-primary-600"
>
{{ item.name }}
</p>
<p class="text-sm text-gray-500">{{ item.description }}</p>
</NuxtLink>
</li>
</template>
<script setup>
defineProps({
item: Object,
required: true,
});
</script>
================================================
FILE: components/Home/Divider.vue
================================================
<template>
<div class="flex items-center justify-center gap-8 pointer-events-none">
<span class="h-0.5 w-0.5 bg-gray-400 dark:bg-white/30"></span>
<span class="h-0.5 w-0.5 bg-gray-400 dark:bg-white/30"></span>
<span class="h-0.5 w-0.5 bg-gray-400 dark:bg-white/30"></span>
</div>
</template>
================================================
FILE: components/Home/FeaturedArticles.vue
================================================
<template>
<div>
<h2 class="uppercase text-xs font-semibold text-gray-400 mb-6">
RECENT ARTICLES
</h2>
<ul class="space-y-16">
<li v-for="(article, id) in articles" :key="id">
<AppArticleCard :article="article" />
</li>
</ul>
<div class="flex items-center justify-center mt-6 text-sm">
<UButton
label="All Articles →"
to="/articles"
variant="link"
color="gray"
/>
</div>
</div>
</template>
<script lang="ts" setup>
const { data: articles } = await useAsyncData("articles-home", () =>
queryContent("/articles")
.sort({ published: -1 })
.limit(3)
.only(["title", "description", "published", "slug", "_path"])
.find()
);
</script>
================================================
FILE: components/Home/FeaturedProjects.vue
================================================
<template>
<div>
<h2 class="uppercase text-xs font-semibold text-gray-400 mb-6">
FEATURED PROEJCTS
</h2>
<div class="space-y-4">
<AppProjectCard
v-for="(project, id) in projects"
:key="id"
:project="project"
/>
</div>
<div class="flex items-center justify-center mt-6 text-sm">
<UButton
label="All Projects →"
to="/projects"
variant="link"
color="gray"
/>
</div>
</div>
</template>
<script lang="ts" setup>
const { data: projects } = await useAsyncData("projects-home", () =>
queryContent("/projects").limit(3).find()
);
</script>
================================================
FILE: components/Home/Intro.vue
================================================
<template>
<div class="space-y-6">
<NuxtImg src="/avatar.png" alt="Fayaz Ahmed"
class="ring-2 border ring-gray-200 border-gray-300 dark:ring-white/10 dark:border-gray-800 hover:ring-4 transition-all duration-300 bg-gray-200 dark:bg-gray-900 rounded-full h-12 w-12 sm:h-16 sm:w-16"
sizes="48px sm:64px" placeholder format="webp" />
<h1 class="text-xl font-bold tracking-tight text-gray-800 dark:text-gray-100">
Hello!
</h1>
<p class="text-gray-900 dark:text-gray-400">
I'm Fayaz, I work as a software, product engineer and designer from
Bengaluru, India. I specialize in building web applications and sites
using Javascript, React, Vue & Node. I've procrastinated building this
website for years but finally it's here, I've carved out my own little
nook on the internet to share my silly experiments, nifty projects, and
thoughts (mostly about tech and design).
</p>
<p class="text-gray-900 dark:text-gray-400">
By day, I'm a Fullstack Developer at
<a href="https://headshotpro.com" target="_blank" class="underline">Headshotpro</a>, and
by night (and weekends), I'm busy tinkering with some random tool or app
that I am building.
</p>
</div>
</template>
<script setup>
useSeoMeta({
title: "Fayaz Ahmed",
description:
"I'm Fayaz, your friendly neighborhood software, product engineer and designer from Bengaluru, India. I specialize in building web applications and sites using Javascript, React, Vue & Node.",
});
</script>
================================================
FILE: components/Home/Newsletter.vue
================================================
<template>
<div>
<div class="mb-6 flex items-center gap-3">
<div
class="flex-none rounded-full p-1 text-primary-500 bg-primary-500/10"
>
<div class="h-1.5 w-1.5 rounded-full bg-current"></div>
</div>
<h2 class="uppercase text-xs font-semibold text-gray-400">
STAY IN TOUCH
</h2>
</div>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Get notified when I publish something new, and unsubscribe at any time.
</p>
<div class="flex items-center gap-3 mt-6">
<UInput
placeholder="Email Address"
icon="i-heroicons-envelope"
class="flex-1"
size="lg"
/>
<UButton label="Join →" size="lg" color="black" />
</div>
</div>
</template>
================================================
FILE: components/Home/SocialLinks.vue
================================================
<template>
<div>
<h2 class="uppercase text-xs font-semibold text-gray-400 mb-4">FIND ME ON</h2>
<div class="space-y-5">
<NuxtLink
v-for="link in links"
:key="link.icon"
:to="link.url"
target="_blank"
external
class="flex items-end gap-4 dark:hover:text-gray-300 group"
>
<span class="text-sm">
{{ link.name }}
</span>
<div
class="flex-1 border-b border-dashed border-gray-300 dark:border-gray-800 group-hover:border-gray-700"
></div>
<Icon :name="link.icon" class="w-6 h-6"></Icon>
</NuxtLink>
</div>
</div>
</template>
<script lang="ts" setup>
const links = [
{
name: "Twitter",
url: "https://twitter.com/fayazara",
icon: "mdi:twitter",
},
{
name: "GitHub",
url: "https://github.com/fayazara",
icon: "mdi:github",
},
{
name: "Linkedin",
url: "https://www.linkedin.com/in/fayaz-aralikatti/",
icon: "mdi:linkedin",
},
{
name: "Telegram",
url: "https://t.me/fayazara",
icon: "mdi:telegram",
},
];
</script>
================================================
FILE: components/content/AnimatedCounter.vue
================================================
<template>
<div class="px-4 py-8 flex items-center justify-center flex-col">
<span
ref="target"
class="flex tabular-nums text-slate-900 dark:text-white text-5xl font-extrabold mb-2 [counter-set:_num_var(--num)] before:content-[counter(num)] animate-counter"
>
<span class="sr-only">{{ targetNumber }}</span
>+
</span>
<UButton color="white" @click="startCounter" class="mt-4" size="xs">
Start Counter
</UButton>
<p class="text-xs mt-2 text-gray-500">
or start the counter when this component is in the viewport
</p>
</div>
</template>
<script setup>
const target = ref(null);
const targetIsVisible = useElementVisibility(target);
const props = defineProps({
targetNumber: {
type: Number,
required: true,
default: 1234,
},
});
const startCounter = () => {
const counter = document.querySelector(".animate-counter");
counter.animate([{ "--num": 0 }, { "--num": props.targetNumber }], {
duration: 1000,
easing: "ease-out",
fill: "forwards",
});
};
watchOnce(targetIsVisible, () => {
startCounter();
});
</script>
<style scoped>
@property --num {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
@keyframes counter {
from {
--num: 0;
}
to {
--num: v-bind(props.targetNumber);
}
}
</style>
================================================
FILE: components/content/CodeView.vue
================================================
<template>
<div class="max-h-96 overflow-auto bg-gray-900 text-sm p-2">
<slot />
</div>
</template>
================================================
FILE: components/content/Credit.vue
================================================
<template>
<div class="text-sm p-4 bg-gray-100 dark:bg-gray-900 text-center">
This component was inspired by
<NuxtLink :to="link" external target="_blank" class="underline font-medium">
{{ label }}</NuxtLink
>.
</div>
</template>
<script setup>
defineProps({
label: String,
link: String,
});
</script>
================================================
FILE: components/content/Encryption.vue
================================================
<template>
<div
class="bg-gray-100 dark:bg-gray-900 relative h-40 text-sm overflow-hidden"
@mousemove="handleOnMove"
@touchmove="handleOnMove"
ref="card"
>
<div
ref="letters"
class="absolute left-0 top-0 [--x:0] [--y:0] h-full w-full text-center text-gray-700 dark:text-gray-300"
style="word-wrap: break-word"
>
<p
class="absolute top-1/2 left-1/2 text-gray-500 text-xs -translate-x-1/2 -translate-y-1/2"
>
Hover/Touch
</p>
</div>
</div>
</template>
<script setup>
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const charsLength = chars.length;
const randomChar = () => chars[Math.floor(Math.random() * charsLength)];
const randomString = (length) => Array.from({ length }, randomChar).join("");
const card = ref(null);
const letters = ref(null);
const updateLetters = (x, y) => {
requestAnimationFrame(() => {
letters.value.style.setProperty("--x", `${x}px`);
letters.value.style.setProperty("--y", `${y}px`);
letters.value.innerText = randomString(600);
});
};
const handleOnMove = (e) => {
const rect = card.value.getBoundingClientRect();
updateLetters(e.clientX - rect.left, e.clientY - rect.top);
};
</script>
================================================
FILE: components/content/HackerButton.vue
================================================
<template>
<div class="px-4 py-8 flex items-center justify-center">
<button
type="button"
class="rounded-md bg-white dark:bg-gray-800 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-white shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-950 relative font-mono"
@click="submit"
@mouseenter="startScrambling"
>
{{ displayText }}
</button>
</div>
</template>
<script setup>
const props = defineProps({
label: String,
});
const displayText = ref(props.label);
const charset = "abcdefghijklmnopqrstuvwxyz";
function randomChars(length) {
return Array.from(
{ length },
() => charset[Math.floor(Math.random() * charset.length)]
).join("");
}
async function scramble(input) {
let prefix = "";
for (let index = 0; index < input.length; index++) {
await new Promise((resolve) => setTimeout(resolve, 50));
prefix += input.charAt(index);
displayText.value = prefix + randomChars(input.length - prefix.length);
}
}
function startScrambling() {
scramble(props.label);
}
const submit = () => {
startScrambling();
setTimeout(() => console.log("Submitted"), props.label.length * 50);
};
watch(
() => props.label,
(newValue) => {
displayText.value = newValue;
}
);
</script>
================================================
FILE: components/content/LabCard.vue
================================================
<template>
<div>
<h2 class="text-sm font-semibold">{{ title }}</h2>
<p class="text-gray-500 text-sm">
{{ description }}
</p>
<div
class="mt-2 border dark:border-white/10 rounded-lg shadow-sm overflow-hidden"
>
<div class="p-2 flex items-center gap-2 border-b dark:border-white/10">
<div class="flex items-center w-full">
<UButton
@click="tab = 'preview'"
label="Preview"
variant="soft"
color="white"
size="xs"
class="relative hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300"
:class="{ 'active-tab': tab === 'preview' }"
/>
<UButton
@click="tab = 'code'"
label="Code"
variant="soft"
color="white"
size="xs"
class="relative hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300"
:class="{ 'active-tab': tab === 'code' }"
/>
<UButton
v-if="showUsageTab"
@click="tab = 'usage'"
label="Usage"
variant="soft"
color="white"
size="xs"
class="relative hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300"
:class="{ 'active-tab': tab === 'usage' }"
/>
<UButton
v-if="showCreditTab"
@click="tab = 'credit'"
label="Credits"
variant="soft"
color="white"
size="xs"
class="relative hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300"
:class="{ 'active-tab': tab === 'credit' }"
/>
</div>
</div>
<div>
<div
v-if="tab === 'preview'"
class="bg-gray-100 dark:bg-gray-900 overflow-hidden"
>
<slot name="preview" />
</div>
<slot v-if="tab === 'code'" name="codebase" />
<slot v-if="tab === 'usage'" name="usage" />
<slot v-if="tab === 'credit'" name="credit" />
</div>
</div>
</div>
</template>
<script setup>
defineProps({
title: String,
description: String,
showUsageTab: {
type: Boolean,
default: true,
},
showCreditTab: {
type: Boolean,
default: false,
},
});
const tab = ref("preview");
</script>
<style scoped>
.active-tab {
@apply after:content-[''] after:absolute after:w-full after:h-0.5 after:bg-primary-500 after:bottom-[-9px] after:left-0 after:pointer-events-none;
}
</style>
================================================
FILE: components/content/Rocket.vue
================================================
<template>
<div
class="py-12 relative overflow-hidden flex items-center justify-center w-full bg-gray-100 dark:bg-gray-900 dark:text-white"
@mouseover="fast = true"
@mouseleave="fast = false"
:style="{ '--streak-speed': streakSpeed }"
>
<span class="rocket" :class="{ shake: fast, move: !fast }">
<Icon name="ph:rocket-duotone" class="h-12 w-12 -rotate-90" />
</span>
<span
v-for="n in 5"
:key="n"
:style="{
top: Math.random() * 100 + '%',
animationDelay: Math.random() * 1 + 's',
animationDuration: streakSpeed,
}"
class="streak absolute left-0 w-1/5 h-0.5 bg-gradient-to-r from-transparent to-black/60 dark:to-white/40"
></span>
</div>
</template>
<script setup>
const fast = ref(false);
const streakSpeed = computed(() => (fast.value ? "0.5s" : "2s"));
</script>
<style scoped>
.rocket.move {
animation: move 1s linear infinite;
}
.rocket.shake {
animation: shake 0.5s linear infinite;
}
.streak {
animation: streaks linear infinite;
animation-duration: var(--streak-speed);
}
@keyframes move {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
@keyframes streaks {
to {
left: 100%;
}
}
@keyframes shake {
2% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
4% {
transform: translate(2.5px, 0.5px) rotate(-0.5deg);
}
6% {
transform: translate(2.5px, 0.5px) rotate(0.5deg);
}
8% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
10% {
transform: translate(1.5px, -1.5px) rotate(-0.5deg);
}
12% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
14% {
transform: translate(0.5px, 1.5px) rotate(-0.5deg);
}
16% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
18% {
transform: translate(1.5px, -1.5px) rotate(0.5deg);
}
20% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
22% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
24% {
transform: translate(0.5px, -0.5px) rotate(-0.5deg);
}
26% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
28% {
transform: translate(0.5px, 0.5px) rotate(0.5deg);
}
30% {
transform: translate(2.5px, 0.5px) rotate(0.5deg);
}
32% {
transform: translate(-0.5px, 0.5px) rotate(0.5deg);
}
34% {
transform: translate(2.5px, 0.5px) rotate(-0.5deg);
}
36% {
transform: translate(0.5px, 2.5px) rotate(0.5deg);
}
38% {
transform: translate(-0.5px, 0.5px) rotate(-0.5deg);
}
40% {
transform: translate(-1.5px, 1.5px) rotate(-0.5deg);
}
42% {
transform: translate(1.5px, 2.5px) rotate(-0.5deg);
}
44% {
transform: translate(-0.5px, 0.5px) rotate(-0.5deg);
}
46% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
48% {
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
}
50% {
transform: translate(-0.5px, -1.5px) rotate(-0.5deg);
}
52% {
transform: translate(-1.5px, 1.5px) rotate(1.5deg);
}
54% {
transform: translate(-1.5px, -1.5px) rotate(-0.5deg);
}
56% {
transform: translate(2.5px, 1.5px) rotate(0.5deg);
}
58% {
transform: translate(-1.5px, -1.5px) rotate(0.5deg);
}
60% {
transform: translate(1.5px, 0.5px) rotate(0.5deg);
}
62% {
transform: translate(-0.5px, -1.5px) rotate(1.5deg);
}
64% {
transform: translate(0.5px, 1.5px) rotate(-0.5deg);
}
66% {
transform: translate(-0.5px, 2.5px) rotate(0.5deg);
}
68% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
70% {
transform: translate(1.5px, -1.5px) rotate(0.5deg);
}
72% {
transform: translate(0.5px, 2.5px) rotate(-0.5deg);
}
74% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
76% {
transform: translate(-0.5px, 0.5px) rotate(0.5deg);
}
78% {
transform: translate(1.5px, -0.5px) rotate(0.5deg);
}
80% {
transform: translate(-0.5px, 2.5px) rotate(-0.5deg);
}
82% {
transform: translate(-1.5px, 0.5px) rotate(1.5deg);
}
84% {
transform: translate(-1.5px, 2.5px) rotate(-0.5deg);
}
86% {
transform: translate(1.5px, -0.5px) rotate(0.5deg);
}
88% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
90% {
transform: translate(0.5px, 0.5px) rotate(0.5deg);
}
92% {
transform: translate(0.5px, -1.5px) rotate(-0.5deg);
}
94% {
transform: translate(0.5px, 0.5px) rotate(-0.5deg);
}
96% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
98% {
transform: translate(1.5px, 2.5px) rotate(0.5deg);
}
0%,
100% {
transform: translate(0, 0) rotate(0);
}
}
</style>
================================================
FILE: components/content/Shapes.vue
================================================
<template>
<div class="px-4 py-8 flex items-center justify-center flex-col">
<div class="flex w-full items-center gap-4">
<div class="flex flex-col items-center gap-3">
<div
class="w-0 h-0 border-x-[30px] border-x-transparent border-b-[50px] border-b-gray-800 dark:border-b-white"
/>
<p class="text-xs font-semibold text-gray-500">Triangle</p>
</div>
<div class="flex flex-col items-center gap-3">
<div
class="relative w-0 h-0 my-[17.5px] mx-0 border-x-[35px] border-x-transparent border-b-[24.5px] border-b-red-500 rotate-[35deg] before:content-[''] before:absolute before:w-0 before:h-0 before:border-b-[28px] before:border-b-red-500 before:border-x-[10.5px] before:border-x-transparent before:top-[-15.75px] before:left-[-22.75px] before:rotate-[-35deg] after:content-[''] after:absolute after:w-0 after:h-0 after:border-x-[35px] after:border-b-[24.5px] after:border-b-red-500 after:border-x-transparent after:top-[1.05px] after:left-[-36.75px] after:rotate-[-70deg]"
></div>
<p class="text-xs font-semibold text-gray-500">Star</p>
</div>
<div class="flex flex-col items-center gap-3">
<div
class="w-0 h-0 border-x-[20px] border-x-transparent border-y-[25px] border-y-indigo-500"
></div>
<p class="text-xs font-semibold text-gray-500">Hourglass</p>
</div>
<div class="flex flex-col items-center gap-3">
<div class="w-20 h-12 bg-teal-500 -skew-x-[16deg]"></div>
<p class="text-xs font-semibold text-gray-500">Parallelogram</p>
</div>
</div>
</div>
</template>
================================================
FILE: components/content/TextRotator.vue
================================================
<template>
<div class="px-4 py-8 flex items-center justify-center">
<div
class="font-extrabold text-lg [text-wrap:balance] text-gray-700 dark:text-gray-200"
>
We design and develop the best
<span
class="inline-flex flex-col h-[calc(theme(fontSize.lg)*theme(lineHeight.tight))] overflow-hidden"
>
<ul
class="block text-left leading-tight [&_li]:block animate-text-slide"
>
<li class="text-indigo-500">Mobile apps</li>
<li class="text-rose-500">Websites</li>
<li class="text-yellow-500">Admin dashboards</li>
<li class="text-teal-500">Landing pages</li>
<li class="text-pink-500">Illustrations</li>
<li class="text-sky-500">Icons</li>
</ul>
</span>
</div>
</div>
</template>
<style>
.animate-text-slide {
animation: text-slide 12.5s cubic-bezier(0.83, 0, 0.17, 1) infinite;
}
@keyframes text-slide {
0%,
15% {
transform: translateY(0%);
}
17%,
32% {
transform: translateY(-16.66%);
}
34%,
49% {
transform: translateY(-33.33%);
}
51%,
66% {
transform: translateY(-50%);
}
68%,
83% {
transform: translateY(-66.66%);
}
85%,
100% {
transform: translateY(-83.33%);
}
}
</style>
================================================
FILE: content/articles/building-your-first-api-with-expressjs-a-beginners-guide.md
================================================
---
title: "Building Your First API with Express.js: A Beginner's Guide"
description: "A beginner-friendly guide to building your first API with Express.js"
published: 2023/11/2
slug: "building-your-first-api-with-expressjs-a-beginners-guide"
---
> This article was created using ChatGPT and meant as a placeholder
## What is Express.js?
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications. It facilitates the rapid development of Node-based web applications and is widely used to build APIs due to its simplicity and performance.
## Step 1: Setting Up Your Environment
Before you start, ensure that you have Node.js installed on your system. You can download it from Node.js official website.
Once Node.js is installed, you can initiate your project:
```bash
mkdir my-express-api
cd my-express-api
npm init -y
```
This creates a new directory for your project and initializes a new Node.js project.
## Step 2: Installing Express.js
Install Express.js using npm (Node Package Manager):
```bash
Copy code
npm install express --save
This command installs Express.js and adds it to your project's dependencies.
```
## Step 3: Creating Your First Express Server
Create a file named app.js in your project directory. This file will be the entry point of your API. Add the following code to app.js:
```js
Copy code
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
```
This code creates a basic Express server that listens on port 3000 and responds with "Hello World!" to HTTP GET requests to the root URL (/).
## Step 4: Running Your Express Server
Run your server using Node.js:
```bash
Copy code
node app.js
Visit http://localhost:3000 in your browser. You should see the message "Hello World!".
```
## Step 5: Building a Simple API
Now, let's expand our server to act as a simple API. For example, let's create an endpoint that returns a list of users.
Add the following code to your app.js:
```javascript
Copy code
let users = [{ name: "Alice" }, { name: "Bob" }];
app.get('/users', (req, res) => {
res.json(users);
});
```
Now, if you visit http://localhost:3000/users, you will see the JSON representation of the users array.
## Step 6: Testing Your API
It’s important to test your API. You can use tools like Postman or curl to test your endpoints.
## Step 7: Next Steps
From here, you can start building more complex APIs. Consider the following:
Implementing CRUD (Create, Read, Update, Delete) operations.
Connecting your API to a database.
Adding authentication and authorization.
Organizing your code with routers and controllers.
## Conclusion
Express.js simplifies the process of building APIs in Node.js. It's a great starting point for developers looking to delve into backend development. With its minimalist approach, you have the freedom to structure your applications as you see fit, making Express.js an invaluable tool in your development toolkit.
================================================
FILE: content/articles/how-to-convert-a-svg-to-png-using-canvas.md
================================================
---
title: "How to convert a SVG to PNG using Canvas"
description: "A simple way to convert a SVG to PNG using Canvas"
published: 2023/11/22
slug: "how-to-convert-a-svg-to-png-using-canvas"
---
> This article was created using ChatGPT and meant as a placeholder
Converting an SVG to a PNG in JavaScript using a canvas element is a handy technique for web developers who need to manipulate vector graphics for various applications. This process essentially involves rendering an SVG image onto a canvas and then converting the canvas to a PNG format. Here's a step-by-step guide on how to achieve this:
1. Prepare the SVG
Ensure your SVG code is ready. You can use an SVG file or an SVG string embedded directly in your JavaScript code. If you're using an external SVG file, you'll need to load it into your application.
2. Create a Canvas Element
You need a canvas element to draw your SVG onto. This can be an existing canvas in your HTML or one created dynamically using JavaScript:
```js
let canvas = document.createElement("canvas");
canvas.width = 500; // Set the canvas width
canvas.height = 500; // Set the canvas height
```
Set the width and height of the canvas to match the desired dimensions of your final PNG.
3. Draw the SVG onto the Canvas
To draw the SVG onto the canvas, you need to convert the SVG into an image and then draw that image on the canvas. This can be done using the Image object in JavaScript:
```js
let img = new Image();
img.onload = function () {
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
};
img.src = "data:image/svg+xml;base64," + btoa(svgString);
```
In this code, replace svgString with your SVG code. If you're using an external SVG file, ensure the file is read and converted into a base64 string.
4. Convert Canvas to PNG
Once your SVG is rendered on the canvas, you can convert the canvas to a PNG image:
```js
let pngUrl = canvas.toDataURL("image/png");
```
This pngUrl is a base64 encoded string representing your PNG image.
5. Use or Save the PNG Image
Now that you have your PNG in base64 format, you can use it as needed in your application. For example, you can display it in an <img> element or download it:
```js
let imgElement = document.createElement("img");
imgElement.src = pngUrl;
document.body.appendChild(imgElement);
// To download the image
let downloadLink = document.createElement("a");
downloadLink.href = pngUrl;
downloadLink.download = "image.png";
downloadLink.click();
```
### Additional Considerations
Cross-Origin Issues: If you're loading an SVG from an external source, you may encounter cross-origin issues. Ensure CORS policies are configured correctly on the server hosting the SVG file.
SVG Features: Some SVG features may not render correctly on canvas, so test your SVGs thoroughly.
Performance: For large SVG files, consider the performance implications of this conversion process.
### Conclusion
Converting SVG to PNG using JavaScript and canvas is a powerful technique that can be integrated into web applications for dynamic image manipulation. By following these steps, developers can effectively translate the versatility of SVGs into the wide compatibility of PNGs.
================================================
FILE: content/lab/1.text-rotator.md
================================================
::LabCard{title="Text Rotator" description="Rotate text with tailwindcss." :showUsageTab=false}
#preview
::TextRotator
::
#codebase
::CodeView
```html
<template>
<div
class="p-4 bg-gray-100 rounded-md border flex flex-col justify-center items-center overflow-hidden"
>
<div class="font-extrabold text-lg [text-wrap:balance] text-gray-700">
We design and develop the best
<span
class="inline-flex flex-col h-[calc(theme(fontSize.lg)*theme(lineHeight.tight))] overflow-hidden"
>
<ul
class="block text-left leading-tight [&_li]:block animate-text-slide"
>
<li class="text-indigo-500">Mobile apps</li>
<li class="text-rose-500">Websites</li>
<li class="text-yellow-500">Admin dashboards</li>
<li class="text-teal-500">Landing pages</li>
<li class="text-pink-500">Illustrations</li>
<li class="text-sky-500">Icons</li>
</ul>
</span>
</div>
</div>
</template>
<style>
.animate-text-slide {
animation: text-slide 12.5s cubic-bezier(0.83, 0, 0.17, 1) infinite;
}
@keyframes text-slide {
0%,
16% {
transform: translateY(0%);
}
20%,
36% {
transform: translateY(-16.66%);
}
40%,
56% {
transform: translateY(-33.33%);
}
60%,
76% {
transform: translateY(-50%);
}
80%,
96% {
transform: translateY(-66.66%);
}
100% {
transform: translateY(-83.33%);
}
}
</style>
```
::
================================================
FILE: content/lab/2.hacker-button.md
================================================
::LabCard{title="Hacker button" description="Randomize text on click with vue"}
#preview
::HackerButton{label="Submit Form"}
::
#codebase
::CodeView
```vue
<template>
<button
type="button"
class="rounded-md bg-white dark:bg-gray-800 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-white shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-950 relative font-mono"
@click="startScrambling"
>
{{ displayText }}
</button>
</template>
<script setup>
const props = defineProps({
label: String,
});
const displayText = ref(props.label);
const charset = "abcdefghijklmnopqrstuvwxyz";
function randomChars(length) {
return Array.from(
{ length },
() => charset[Math.floor(Math.random() * charset.length)]
).join("");
}
async function scramble(input) {
let prefix = "";
for (let index = 0; index < input.length; index++) {
await new Promise((resolve) => setTimeout(resolve, 50));
prefix += input.charAt(index);
displayText.value = prefix + randomChars(input.length - prefix.length);
}
}
const startScrambling = () => {
scramble(props.label);
setTimeout(() => console.log("Submitted"), props.label.length * 50);
};
watch(
() => props.label,
(newValue) => {
displayText.value = newValue;
}
);
</script>
```
::
#usage
::CodeView
```vue
<HackerButton label="Submit Form' />
```
::
================================================
FILE: content/lab/3.animated-number-counter.md
================================================
::LabCard{title="Animated number counter" description="Animate numbers with a counter using tailwindcss and vue."}
#preview
::AnimatedCounter{:targetNumber="4000"}
::
#codebase
::CodeView
```vue
<template>
<div
class="p-4 bg-gray-100 dark:bg-gray-900 flex flex-col justify-center items-center overflow-hidden"
>
<span
class="flex tabular-nums text-slate-900 dark:text-white text-5xl font-extrabold mb-2 [counter-set:_num_var(--num)] before:content-[counter(num)] animate-counter"
>
<span class="sr-only">{{ targetNumber }}</span
>+
</span>
</div>
</template>
<script setup>
const props = defineProps({
targetNumber: {
type: Number,
required: true,
default: 1234,
},
});
const startCounter = () => {
const counter = document.querySelector(".animate-counter");
counter.animate([{ "--num": 0 }, { "--num": props.targetNumber }], {
duration: 1000,
easing: "ease-out",
fill: "forwards",
});
};
</script>
<style scoped>
@property --num {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
@keyframes counter {
from {
--num: 0;
}
to {
--num: v-bind(props.targetNumber);
}
}
</style>
```
::
#usage
::CodeView
```vue
<AnimatedCounter targetNumber="1234" />
```
::
================================================
FILE: content/lab/4.shapes-with-tailwindcss.md
================================================
::LabCard{title="Shapes with Tailwindcss" description="Some random shapes made with tailwindcss" :showUsageTab=false}
#preview
::Shapes
::
#codebase
::CodeView
```html
<!-- Triangle -->
<div
class="w-0 h-0 border-x-[30px] border-x-transparent border-b-[50px] border-b-gray-800"
/>
<!-- Star -->
<div
class="relative w-0 h-0 my-[17.5px] mx-0 border-x-[35px] border-x-transparent border-b-[24.5px] border-b-red-500 rotate-[35deg] before:content-[''] before:absolute before:w-0 before:h-0 before:border-b-[28px] before:border-b-red-500 before:border-x-[10.5px] before:border-x-transparent before:top-[-15.75px] before:left-[-22.75px] before:rotate-[-35deg] after:content-[''] after:absolute after:w-0 after:h-0 after:border-x-[35px] after:border-b-[24.5px] after:border-b-red-500 after:border-x-transparent after:top-[1.05px] after:left-[-36.75px] after:rotate-[-70deg]"
/>
<!-- Hourglass -->
<div
class="w-0 h-0 border-x-[20px] border-x-transparent border-y-[25px] border-y-indigo-500"
/>
<!-- Parallelogram -->
<div class="w-20 h-12 bg-teal-500 -skew-x-[16deg]" />
```
::
================================================
FILE: content/lab/5.rocket.md
================================================
::LabCard{title="Rocket" description="A super fast rocket animation" :showUsageTab=false :showCreditTab=true}
#preview
::Rocket
::
#codebase
::CodeView
```html
<template>
<div
class="py-12 relative overflow-hidden flex items-center justify-center"
@mouseover="fast = true"
@mouseleave="fast = false"
:style="{ '--streak-speed': streakSpeed }"
>
<span class="rocket" :class="{ shake: fast, move: !fast }">
<Icon name="ph:rocket-duotone" class="h-12 w-12 -rotate-90" />
</span>
<span
v-for="n in 5"
:key="n"
:style="{
top: Math.random() * 100 + '%',
animationDelay: Math.random() * 1 + 's',
animationDuration: streakSpeed,
}"
class="streak absolute left-0 w-1/5 h-0.5 bg-gradient-to-r from-transparent to-black/60"
></span>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const fast = ref(false);
const streakSpeed = computed(() => (fast.value ? "0.5s" : "2s"));
</script>
<style scoped>
.rocket.move {
animation: move 1s linear infinite;
}
.rocket.shake {
animation: shake 0.5s linear infinite;
}
.streak {
animation: streaks linear infinite;
animation-duration: var(--streak-speed);
}
@keyframes move {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
@keyframes streaks {
to {
left: 100%;
}
}
@keyframes shake {
2% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
4% {
transform: translate(2.5px, 0.5px) rotate(-0.5deg);
}
6% {
transform: translate(2.5px, 0.5px) rotate(0.5deg);
}
8% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
10% {
transform: translate(1.5px, -1.5px) rotate(-0.5deg);
}
12% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
14% {
transform: translate(0.5px, 1.5px) rotate(-0.5deg);
}
16% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
18% {
transform: translate(1.5px, -1.5px) rotate(0.5deg);
}
20% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
22% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
24% {
transform: translate(0.5px, -0.5px) rotate(-0.5deg);
}
26% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
28% {
transform: translate(0.5px, 0.5px) rotate(0.5deg);
}
30% {
transform: translate(2.5px, 0.5px) rotate(0.5deg);
}
32% {
transform: translate(-0.5px, 0.5px) rotate(0.5deg);
}
34% {
transform: translate(2.5px, 0.5px) rotate(-0.5deg);
}
36% {
transform: translate(0.5px, 2.5px) rotate(0.5deg);
}
38% {
transform: translate(-0.5px, 0.5px) rotate(-0.5deg);
}
40% {
transform: translate(-1.5px, 1.5px) rotate(-0.5deg);
}
42% {
transform: translate(1.5px, 2.5px) rotate(-0.5deg);
}
44% {
transform: translate(-0.5px, 0.5px) rotate(-0.5deg);
}
46% {
transform: translate(2.5px, 1.5px) rotate(-0.5deg);
}
48% {
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
}
50% {
transform: translate(-0.5px, -1.5px) rotate(-0.5deg);
}
52% {
transform: translate(-1.5px, 1.5px) rotate(1.5deg);
}
54% {
transform: translate(-1.5px, -1.5px) rotate(-0.5deg);
}
56% {
transform: translate(2.5px, 1.5px) rotate(0.5deg);
}
58% {
transform: translate(-1.5px, -1.5px) rotate(0.5deg);
}
60% {
transform: translate(1.5px, 0.5px) rotate(0.5deg);
}
62% {
transform: translate(-0.5px, -1.5px) rotate(1.5deg);
}
64% {
transform: translate(0.5px, 1.5px) rotate(-0.5deg);
}
66% {
transform: translate(-0.5px, 2.5px) rotate(0.5deg);
}
68% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
70% {
transform: translate(1.5px, -1.5px) rotate(0.5deg);
}
72% {
transform: translate(0.5px, 2.5px) rotate(-0.5deg);
}
74% {
transform: translate(0.5px, -1.5px) rotate(1.5deg);
}
76% {
transform: translate(-0.5px, 0.5px) rotate(0.5deg);
}
78% {
transform: translate(1.5px, -0.5px) rotate(0.5deg);
}
80% {
transform: translate(-0.5px, 2.5px) rotate(-0.5deg);
}
82% {
transform: translate(-1.5px, 0.5px) rotate(1.5deg);
}
84% {
transform: translate(-1.5px, 2.5px) rotate(-0.5deg);
}
86% {
transform: translate(1.5px, -0.5px) rotate(0.5deg);
}
88% {
transform: translate(-1.5px, 0.5px) rotate(0.5deg);
}
90% {
transform: translate(0.5px, 0.5px) rotate(0.5deg);
}
92% {
transform: translate(0.5px, -1.5px) rotate(-0.5deg);
}
94% {
transform: translate(0.5px, 0.5px) rotate(-0.5deg);
}
96% {
transform: translate(2.5px, 2.5px) rotate(1.5deg);
}
98% {
transform: translate(1.5px, 2.5px) rotate(0.5deg);
}
0%,
100% {
transform: translate(0, 0) rotate(0);
}
}
</style>
```
::
#credit
::Credit{link="https://loops.so?ref=fayazahmed.com" label="The hover animation on loops.so"}
::
================================================
FILE: content/lab/6.encryption.md
================================================
::LabCard{title="Encrypted text" description="The encrypted text from evervault" :showUsageTab=false :showCreditTab=true}
#preview
::Encryption
::
#codebase
::CodeView
```html
<template>
<div
class="bg-gray-100 dark:bg-gray-900 relative h-40 text-sm overflow-hidden"
@mousemove="handleOnMove"
ref="card"
>
<div
ref="letters"
class="absolute left-0 top-0 [--x:0] [--y:0] h-full w-full"
style="word-wrap: break-word"
></div>
</div>
</template>
<script setup>
const chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const charsLength = chars.length;
const randomChar = () => chars[Math.floor(Math.random() * charsLength)];
const randomString = (length) => Array.from({ length }, randomChar).join("");
const card = ref(null);
const letters = ref(null);
const updateLetters = (x, y) => {
requestAnimationFrame(() => {
letters.value.style.setProperty("--x", `${x}px`);
letters.value.style.setProperty("--y", `${y}px`);
letters.value.innerText = randomString(600);
});
};
const handleOnMove = (e) => {
const rect = card.value.getBoundingClientRect();
updateLetters(e.clientX - rect.left, e.clientY - rect.top);
};
</script>
```
::
#credit
::Credit{link="https://youtu.be/oIm6qKTtmH4?si=FoowKF1Sk4lGVApw" label="This Hyperplexed video"}
::
================================================
FILE: content/projects/1.feedbackjar.json
================================================
{
"name": "Feedbackjar",
"url": "https://feedbackjar.app",
"description": "Open source feedback, roadmaps and changelogs",
"thumbnail": "/projects/feedbackjar.png",
"status": "WIP",
"opensource": true
}
================================================
FILE: content/projects/10.fluenticons.json
================================================
{
"name": "Fluenticons",
"url": "https://fluenticons.co",
"description": "Microsoft fluenticons viewer",
"thumbnail": "/projects/fluenticons.png",
"status": "Acquired",
"opensource": true
}
================================================
FILE: content/projects/11.appydev.json
================================================
{
"name": "Appydev",
"url": "https://appydev.co",
"description": "Tools for designers and developers",
"thumbnail": "/projects/appydev.png",
"status": "Acquired"
}
================================================
FILE: content/projects/12.gitstars.json
================================================
{
"name": "Gitstars",
"url": "",
"description": "Better github trending page",
"thumbnail": "/projects/gitstars.png",
"status": "Dead"
}
================================================
FILE: content/projects/13.tvflix.json
================================================
{
"name": "Tvflix",
"url": "https://tvflix.netlify.app",
"description": "IPTV player with 100K+ tv stations in browser",
"thumbnail": "/projects/tvflix.png",
"status": "Dead"
}
================================================
FILE: content/projects/2.feedful.json
================================================
{
"name": "Feedful",
"url": "https://feedful.app",
"description": "Modern news reader in tweetdeck style",
"thumbnail": "/projects/feedful.png",
"status": "Active"
}
================================================
FILE: content/projects/3.1.hawa.json
================================================
{
"name": "Hawa",
"url": "https://github.com/fayazara/hawa",
"description": "A simple white noise app which sits in the menubar",
"thumbnail": "/projects/hawa.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/3.123.sketch-to-ui.json
================================================
{
"name": "Sketch to UI",
"url": "https://github.com/fayazara/sketch-to-ui",
"description": "Covert bad hand drawn UI sketch to code using GPT 4 Vision model",
"thumbnail": "/projects/sketch-to-ui.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/3.2.pocketbase-nuxt.json
================================================
{
"name": "Pocketbase Nuxt",
"url": "https://github.com/fayazara/pocketbase-nuxt",
"description": "A starter template for Nuxt.js with Pocketbase",
"thumbnail": "/projects/pb-nuxt.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/3.formdata.json
================================================
{
"name": "Formdata",
"url": "https://formdata.cc",
"description": "Relay form data to your email for free",
"thumbnail": "/projects/formdata.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/4.simpleonlinetools.json
================================================
{
"name": "Simple Online Tools",
"url": "https://simpleonline.tools",
"description": "Simple online tools for everyday use",
"thumbnail": "/projects/simpleonline-tools.png",
"status": "WIP",
"opensource": true
}
================================================
FILE: content/projects/5.imbox.json
================================================
{
"name": "Imbox",
"url": "https://dub.sh/imbox",
"description": "Temporary inbox as a chrome extension",
"thumbnail": "/projects/imbox.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/6.bring-back-twitter.json
================================================
{
"name": "Bring back the bird",
"url": "https://github.com/fayazara/bring-back-twitter-bird",
"description": "Chrome extension to replace the new Twitter logo with the original bird",
"thumbnail": "/projects/twitter.svg",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/6.onelink.json
================================================
{
"name": "Onelink",
"url": "https://onelink-nu.vercel.app/",
"description": "An experimental link-in-bio tool, where the data lives in the URL.",
"thumbnail": "/projects/onelink.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/7.logspot.json
================================================
{
"name": "Logspot",
"url": "https://logspot.vercel.app/",
"description": "A beautiful open source change log template, with widgets included",
"thumbnail": "/projects/logspot.png",
"status": "Active",
"opensource": true
}
================================================
FILE: content/projects/8.iconbuddy.json
================================================
{
"name": "Iconbuddy",
"url": "https://iconbuddy.app",
"description": "200k+ open source icons in one place",
"thumbnail": "/projects/iconbuddy.png",
"status": "Acquired"
}
================================================
FILE: content/projects/9.postperfect.json
================================================
{
"name": "Postperfect",
"url": "https://postperfect.xyz",
"description": "Chatgpt for your tweets",
"thumbnail": "/projects/postperfect.png",
"status": "Acquired"
}
================================================
FILE: content/uses/Tableplus.json
================================================
{
"name": "Tableplus",
"description": "The only sane SQL client I've seen for Mac.",
"url": "https://tableplus.com/",
"category": "software"
}
================================================
FILE: content/uses/apple-airpods-3.json
================================================
{
"name": "Apple airpods 3",
"description": "Probably my most used item after my phone and laptop. I use them for everything from listening to music to taking calls. They are super convenient and the sound quality is great.",
"url": "https://www.apple.com/in/airpods-3rd-generation/",
"category": "hardware",
"thumbnail": "/uses/apple-airpods-3.png"
}
================================================
FILE: content/uses/apple-iphone-12.json
================================================
{
"name": "Apple iPhone 12",
"description": "I dont usually invest and upgrade my phone frequently, I used my last phone for 5 years, I will use this one for 5 years too.",
"url": "https://www.gsmarena.com/apple_iphone_12-10509.php",
"category": "hardware",
"thumbnail": "/uses/apple-iphone-12.png"
}
================================================
FILE: content/uses/apple-watch-series-7.json
================================================
{
"name": "Apple watch series 7",
"description": "I use this mostly to track my workouts",
"url": "https://www.apple.com/by/apple-watch-series-7/",
"category": "hardware",
"thumbnail": "/uses/apple-watch.png"
}
================================================
FILE: content/uses/bear.json
================================================
{
"name": "Bear",
"description": "The lack of code highlightin and markdown support in Apple notes, made me move to Bear and I am not looking back now.",
"url": "https://bear.app/",
"category": "software"
}
================================================
FILE: content/uses/cleanshot.json
================================================
{
"name": "Cleanshot",
"description": "You might be wondering why would I pay for a screenshot taking tool. Well, it's is not just a screenshot taking tool. It is a complete package of tools that you can use to take screenshots, record your screen, annotate your screenshots, and much more. It's worth it.",
"url": "https://cleanshot.com/",
"category": "software"
}
================================================
FILE: content/uses/daily-objects-turf-desk-mat.json
================================================
{
"name": "Daily Objects - Turf 2.0 Felt Desk Mat - Grey",
"description": "This is clean, not too expensive and looks great",
"url": "https://www.dailyobjects.com/turf-2-0-felt-desk-mat-mouse-pad-grey",
"category": "desk"
}
================================================
FILE: content/uses/dell-nero-dock.json
================================================
{
"name": "Dell D6000 Usb 3.0 (3.1 Gen 1) Type-C Nero Port Black",
"description": "My dock for my workstation, this is super expensive, but I got a refurbished one for 10% of the original price, thanks to RemoteIndian slack channel, they coordinated with a seller and ordered in bulk, I got one of them.",
"url": "https://www.amazon.in/DELL-D6000-USB-Type-C-Nero/dp/B072K6HJBN/",
"category": "hardware",
"thumbnail": "/uses/dell-dock.png"
}
================================================
FILE: content/uses/fake-plants.json
================================================
{
"name": "Fake plants",
"description": "My desk was looking bland, so I bought some fake plants to make it look lively.",
"url": "https://www.amazon.in/gp/product/B08Q4FNYL6",
"category": "desk"
}
================================================
FILE: content/uses/featherlite-helix-chair.json
================================================
{
"name": "Featherlite Helix Chair",
"description": "This is a really bad chair, but works for now 😅. Will save up to buy a better one.",
"url": "https://featherlitefurniture.com/product/helix-high-back-chair/",
"category": "desk"
}
================================================
FILE: content/uses/gifski.json
================================================
{
"name": "Gifski",
"description": "The best Video to GIF creator for Mac",
"url": "https://gif.ski/",
"category": "software"
}
================================================
FILE: content/uses/httpie.json
================================================
{
"name": "HTTPie",
"description": "An API testing tool with a beautiful UI, free of nonsense and unnecessary features.",
"url": "https://httpie.io/app",
"category": "software"
}
================================================
FILE: content/uses/ikea-headphone-stand.json
================================================
{
"name": "Ikea MÖJLIGHET headphone stand, black",
"description": "This is super cheap, just ₹199, looks really clean, keeps my desk tidy.",
"url": "https: //www.ikea.com/in/en/p/moejlighet-headset-tablet-stand-black-80434278",
"category": "desk"
}
================================================
FILE: content/uses/jbl-csum-microphone.json
================================================
{
"name": "JBL CSUM10 compact USB microphone",
"description": "I don't use this. Got it from my previous companies WFH Kit.",
"url": "https://www.amazon.in/gp/product/B092PQBKR9",
"category": "hardware",
"thumbnail": "/uses/jbl-csum.png"
}
================================================
FILE: content/uses/keychron-k2.json
================================================
{
"name": "Keychron K2",
"description": "I am little obsessed with keyboards and this is my current favorite. It's a 75% keyboard with a great build quality and a nice tactile feel. I use this whenever I am in mood of typing on a mechanical keyboard.",
"url": "https://keychron.in/product/keychron-k2-v-2/",
"category": "hardware",
"thumbnail": "/uses/keychron-k2.png"
}
================================================
FILE: content/uses/krisp.json
================================================
{
"name": "Krisp AI",
"description": "Mute background noise during calls. Works so good, I have never gone a meeting without it, since I first installed.",
"url": "https://krisp.ai/",
"category": "software"
}
================================================
FILE: content/uses/lenovo-monitor.json
================================================
{
"name": "Lenovo Q24 FHD monitor",
"description": "I so want a new monitor, but this works for now and it's not bad.",
"url": "https://www.amazon.in/Lenovo-23-8-inch-Ultraslim-2xSpeakers-Q24i-1L/dp/B095348ZMH?th=1",
"category": "hardware",
"thumbnail": "/uses/lenovo-q24-monitor.png"
}
================================================
FILE: content/uses/logitech-mx-keys.json
================================================
{
"name": "Logitech MX keys mini (Mac)",
"description": "I use this as my keyboard, when I am at my desk",
"url": "https://www.logitech.com/en-in/products/keyboards/mx-keys-mini-for-mac.920-010528.html",
"category": "hardware",
"thumbnail": "/uses/logitech-mx-keys.png"
}
================================================
FILE: content/uses/logitech-mx-master-3s.json
================================================
{
"name": "Logitech MX Master 3s",
"description": "This was a little expensive, but I needed a mouse that was really good and, which would last a long time. I actually got a really good deal on flipkart and got this for 5k",
"url": "https://www.logitech.com/en-in/products/mice/mx-master-3s.html",
"category": "hardware",
"thumbnail": "/uses/logitech-mx-master.png"
}
================================================
FILE: content/uses/macbook.json
================================================
{
"name": "Macbook pro 13 (2020)",
"description": "The Intel i5 version. 16GB RAM, 512GB SSD",
"url": "https://support.apple.com/kb/SP819?locale=en_US",
"category": "hardware",
"thumbnail": "/uses/apple-macbook-pro.png"
}
================================================
FILE: content/uses/miyoo-mini.json
================================================
{
"name": "Miyoo Mini v2",
"description": "I really enjoy playing retro games. This is one of the best retro handheld consoles I have seen.",
"url": "https://www.keepretro.com/products/miyoo-mini",
"category": "others"
}
================================================
FILE: content/uses/monitorcontrol.json
================================================
{
"name": "MonitorControl",
"description": "Lets me control my external monitor brightness and volume directly",
"url": "https://monitorcontrol.app/",
"category": "software"
}
================================================
FILE: content/uses/purple-ark-sit-stand-desk.json
================================================
{
"name": "Purple ark sit/stand desk",
"description": "I've always wanted a sit/stand desk and I love this one, it's sturdy and has a nice finish.",
"url": "https://www.purpleark.in/products/sit-stand-desk-electric",
"category": "desk"
}
================================================
FILE: content/uses/raycast.json
================================================
{
"name": "Raycast",
"description": "My used application in my computer. I don' think I've ever relied on a software as much as Raycast",
"url": "https://raycast.com/",
"category": "software"
}
================================================
FILE: content/uses/reminders-menubar.json
================================================
{
"name": "Reminders Menubar",
"description": "Apple reminders in your menubar",
"url": "https://github.com/DamascenoRafael/reminders-menubar",
"category": "software"
}
================================================
FILE: content/uses/sony-playstation-4.json
================================================
{
"name": "Sony Play station 4",
"description": "I don't play it as much anymore, but I do play story based games sometimes",
"url": "https://en.wikipedia.org/wiki/PlayStation_4",
"category": "others"
}
================================================
FILE: content/uses/sony-wh-1000xm4.json
================================================
{
"name": "SONY WH-1000XM4 Bluetooth Headset - Black",
"description": "Essential when you work from home and want to block out the noise of the world. Too expensive but it came with my previous companies WFH budget.",
"url": "https://www.flipkart.com/sony-wh-1000xm4-bluetooth-headset/p/itm9f84f49ad6ac8",
"category": "hardware",
"thumbnail": "/uses/sony-xm4.png"
}
================================================
FILE: content/uses/texts.json
================================================
{
"name": "Texts",
"description": "Texts is so good, I stopped paying for Netflix and moved that to Texts.",
"url": "https://texts.com/",
"category": "software"
}
================================================
FILE: content/uses/vscode.json
================================================
{
"name": "VS Code",
"description": "No brainer, my primary code editor. I use it for everything from writing code to writing this website.",
"url": "https://code.visualstudio.com/",
"category": "software"
}
================================================
FILE: content/uses/zed.json
================================================
{
"name": "Zed",
"description": "A new code editor, I am waiting for them to add features like plugins, once it is done, I will leave VS Code for good.",
"url": "https://zed.dev/",
"category": "software"
}
================================================
FILE: nuxt.config.ts
================================================
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
"@nuxt/ui",
"nuxt-icon",
"@nuxtjs/google-fonts",
"@nuxtjs/fontaine",
"@nuxt/image",
"@nuxt/content",
"@nuxthq/studio",
"@vueuse/nuxt"
],
ui: {
icons: ["heroicons", "lucide"],
},
app: {
pageTransition: { name: "page", mode: "out-in" },
head: {
htmlAttrs: {
lang: "en",
class: "h-full",
},
bodyAttrs: {
class: "antialiased bg-gray-50 dark:bg-black min-h-screen",
},
},
},
content: {
highlight: {
theme: "github-dark",
},
},
googleFonts: {
display: "swap",
families: {
Inter: [400, 500, 600, 700, 800, 900],
},
},
});
================================================
FILE: package.json
================================================
{
"name": "nuxt-app",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@nuxt/content": "^2.10.0",
"@nuxt/devtools": "1.0.5",
"@nuxthq/studio": "1.0.6",
"@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "3.1.1",
"nuxt": "3.9.1",
"nuxt-icon": "0.6.7",
"prettier": "3.1.1",
"prettier-plugin-tailwindcss": "0.5.9",
"yarn-upgrade-all": "^0.7.2"
},
"dependencies": {
"@iconify-json/lucide": "1.1.145",
"@nuxt/image": "1.1.0",
"@nuxt/ui": "2.11.1",
"vue-use-fixed-header": "^2.0.0"
}
}
================================================
FILE: pages/articles/[slug].vue
================================================
<template>
<main class="min-h-screen">
<div
class="prose dark:prose-invert prose-blockquote:not-italic prose-pre:bg-gray-900 prose-img:ring-1 prose-img:ring-gray-200 dark:prose-img:ring-white/10 prose-img:rounded-lg"
>
<ContentDoc v-slot="{ doc }" tag="article">
<article>
<h1>{{ doc.title }}</h1>
<ContentRenderer :value="doc" />
</article>
</ContentDoc>
</div>
</main>
</template>
<script setup>
const route = useRoute();
const { slug } = route.params;
useSeoMeta({
ogImage: `https://fayazahmed.com/articles/${slug}.png`,
twitterCard: "summary_large_image",
articleAuthor: "Fayaz Ahmed",
});
</script>
<style>
.prose h2 a,
.prose h3 a {
@apply no-underline;
}
</style>
================================================
FILE: pages/articles/index.vue
================================================
<template>
<main class="min-h-screen">
<AppHeader class="mb-16" title="Articles" :description="description" />
<ul class="space-y-16">
<li v-for="(article, id) in articles" :key="id">
<AppArticleCard :article="article" />
</li>
</ul>
</main>
</template>
<script setup>
const description =
"All of my long-form thoughts on programming, user interfaces, product design, and more, collected in chronological order.";
useSeoMeta({
title: "Articles | Fayaz Ahmed",
description,
});
const { data: articles } = await useAsyncData("all-articles", () =>
queryContent("/articles").sort({ published: -1 }).find()
);
</script>
================================================
FILE: pages/bookmarks.vue
================================================
<template>
<main class="min-h-screen">
<AppHeader class="mb-8" title="Bookmarks" :description="description" />
<ul class="space-y-2">
<li v-for="bookmark in bookmarks" :key="bookmark.id">
<a
:href="bookmark.url"
target="_blank"
class="flex items-center gap-3 hover:bg-gray-100 dark:hover:bg-white/10 p-2 rounded-lg -m-2 text-sm min-w-0"
>
<UAvatar
:src="getThumbnail(bookmark.url)"
:alt="bookmark.label"
:ui="{ rounded: 'rounded-md' }"
/>
<p class="truncate text-gray-700 dark:text-gray-200">
{{ bookmark.label }}
</p>
<span class="flex-1"></span>
<span class="text-xs font-medium text-gray-400 dark:text-gray-600">
{{ getHost(bookmark.url) }}
</span>
</a>
</li>
</ul>
</main>
</template>
<script setup>
const description =
"Awesome things I've found on the internet. This page is still WIP, I want to add search like bmrks.com";
useSeoMeta({
title: "Bookmarks | Fayaz Ahmed",
description,
});
const bookmarks = [
{
id: 1,
label: "Adam Wathan - Tailwind CSS Best Practice Patterns",
url: "https://www.youtube.com/watch?v=J_7_mnFSLDg",
},
{
id: 2,
label: "Dicebear Awesome avatars",
url: "https://www.dicebear.com/",
},
{
id: 3,
label: "Circuit design stock image",
url: "https://images.unsplash.com/photo-1592659762303-90081d34b277?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2873&q=80",
},
{
id: 4,
label: "Beautiful Gradient Generator",
url: "https://www.joshwcomeau.com/gradient-generator/",
},
{
id: 5,
label: "3D device mockups",
url: "https://deviceframes.com/",
},
{
id: 6,
label: "Box shadow examples",
url: "https://getcssscan.com/css-box-shadow-examples",
},
{
id: 7,
label: "Octupos Illustration",
url: "https://refine.new/",
},
{
id: 8,
label: "Metalab agency",
url: "https://www.metalab.com/",
},
{
id: 9,
label: "Tines - Beautiful landing page",
url: "https://www.tines.com/product",
},
{
id: 10,
label: "SVG Spinners",
url: "https://github.com/n3r4zzurr0/svg-spinners",
},
{
id: 11,
label: "ASCII Flow - Text based image drawing",
url: "https://asciiflow.com/#/",
},
{
id: 12,
label: "REQRES Mock apis for testing",
url: "https://reqres.in/",
},
{
id: 13,
label: "Haikie - SVG background generator",
url: "https://app.haikei.app/",
},
{
id: 14,
label: "IP API",
url: "https://ipapi.is/",
},
{
id: 15,
label: "Rakko Tools",
url: "https://en.rakko.tools/",
},
];
function getHost(url) {
const parsedUrl = new URL(url);
let host = parsedUrl.host;
if (host.startsWith("www.")) {
host = host.substring(4);
}
return host;
}
function getThumbnail(url) {
const host = getHost(url);
return `https://logo.clearbit.com/${host}`;
}
</script>
================================================
FILE: pages/index.vue
================================================
<template>
<main class="min-h-screen">
<div class="space-y-24">
<HomeIntro />
<HomeSocialLinks />
<HomeFeaturedProjects />
<HomeFeaturedArticles />
<HomeNewsletter />
</div>
</main>
</template>
================================================
FILE: pages/lab.vue
================================================
<template>
<main class="min-h-screen">
<AppHeader class="mb-12" title="Lab" :description="description" />
<div class="space-y-24">
<ContentList path="/lab" v-slot="{ list }">
<ContentQuery
v-for="item in list"
:key="item._path"
:path="item._path"
find="one"
v-slot="{ data }"
>
<ContentRenderer>
<ContentRendererMarkdown :value="data" />
</ContentRenderer>
</ContentQuery>
</ContentList>
</div>
</main>
</template>
<script setup>
const description = "Some random experiments with UI I do in my free time.";
useSeoMeta({
title: "Lab | Fayaz Ahmed",
description,
});
</script>
================================================
FILE: pages/projects.vue
================================================
<template>
<main class="min-h-screen">
<AppHeader class="mb-12" title="Projects" :description="description" />
<div class="space-y-4">
<AppProjectCard
v-for="(project, id) in projects"
:key="id"
:project="project"
/>
</div>
</main>
</template>
<script setup>
const description =
"I’ve worked on tons of little projects over the years but these are the ones that I’m most proud of. Many of them are open-source, so if you see something that piques your interest, check out the code and contribute if you have ideas for how it can be improved.";
useSeoMeta({
title: "Projects | Fayaz Ahmed",
description,
});
const { data: projects } = await useAsyncData("projects-all", () =>
queryContent("/projects").find()
);
</script>
================================================
FILE: pages/whats-in-my-bag.vue
================================================
<template>
<main class="min-h-screen">
<AppHeader
class="mb-12"
title="What's in my bag?"
:description="description"
/>
<div class="space-y-24">
<ul class="space-y-8">
<AppUsesHeader title="Hardware" />
<AppUsesItem v-for="(item, id) in hardware" :key="id" :item="item" />
</ul>
<ul class="space-y-8">
<AppUsesHeader title="Software" />
<AppUsesItem v-for="(item, id) in software" :key="id" :item="item" />
</ul>
<ul class="space-y-8">
<AppUsesHeader title="Desk" />
<AppUsesItem v-for="(item, id) in desk" :key="id" :item="item" />
</ul>
<ul class="space-y-8">
<AppUsesHeader title="Other" />
<AppUsesItem v-for="(item, id) in other" :key="id" :item="item" />
</ul>
</div>
</main>
</template>
<script setup>
const description =
"Software I use, gadgets I love, and other things I recommend. Here’s a big list of all of my favorite stuff.";
useSeoMeta({
title: "Things I use | Fayaz Ahmed",
description,
});
const { data: items } = await useAsyncData("uses", () =>
queryContent("/uses").find()
);
const hardware = items.value.filter((item) => item.category === "hardware");
const software = items.value.filter((item) => item.category === "software");
const desk = items.value.filter((item) => item.category === "desk");
const other = items.value.filter((item) => item.category === "others");
</script>
================================================
FILE: tailwind.config.ts
================================================
import type { Config } from "tailwindcss";
module.exports = {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}",
"./app.vue",
],
theme: {
extend: {
boxShadow: {
zoop: "rgba(102, 109, 128, 0.08) 0px 1.2672px 1.2672px 0px, rgba(102, 109, 128, 0.08) 0px 5.06879px 10.1376px 0px",
zoopdark:
"rgba(10, 10, 10, 0.2) 0px 1.2672px 1.2672px 0px, rgba(10, 10, 10, 0.2) 0px 5.06879px 10.1376px 0px",
},
},
fontFamily: {
sans: [
"Inter",
"Avenir Next",
"Roboto",
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
"Ubuntu",
'"Helvetica Neue"',
"Arial",
'"Noto Sans"',
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
mono: [
"Cascadia Code",
"ui-monospace",
"SFMono-Regular",
"Menlo",
"Monaco",
"Consolas",
"Liberation Mono",
"Courier New",
"monospace",
],
},
},
plugins: [require("@tailwindcss/typography")],
} satisfies Config;
================================================
FILE: tsconfig.json
================================================
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}
gitextract_pxr4h3_l/ ├── .gitignore ├── .npmrc ├── .prettierrc ├── .vscode/ │ └── settings.json ├── README.md ├── app.config.ts ├── app.vue ├── components/ │ ├── App/ │ │ ├── ArticleCard.vue │ │ ├── Footer.vue │ │ ├── Header.vue │ │ ├── Navbar.vue │ │ ├── ProjectCard.vue │ │ ├── ThemeToggle.vue │ │ ├── UsesHeader.vue │ │ └── UsesItem.vue │ ├── Home/ │ │ ├── Divider.vue │ │ ├── FeaturedArticles.vue │ │ ├── FeaturedProjects.vue │ │ ├── Intro.vue │ │ ├── Newsletter.vue │ │ └── SocialLinks.vue │ └── content/ │ ├── AnimatedCounter.vue │ ├── CodeView.vue │ ├── Credit.vue │ ├── Encryption.vue │ ├── HackerButton.vue │ ├── LabCard.vue │ ├── Rocket.vue │ ├── Shapes.vue │ └── TextRotator.vue ├── content/ │ ├── articles/ │ │ ├── building-your-first-api-with-expressjs-a-beginners-guide.md │ │ └── how-to-convert-a-svg-to-png-using-canvas.md │ ├── lab/ │ │ ├── 1.text-rotator.md │ │ ├── 2.hacker-button.md │ │ ├── 3.animated-number-counter.md │ │ ├── 4.shapes-with-tailwindcss.md │ │ ├── 5.rocket.md │ │ └── 6.encryption.md │ ├── projects/ │ │ ├── 1.feedbackjar.json │ │ ├── 10.fluenticons.json │ │ ├── 11.appydev.json │ │ ├── 12.gitstars.json │ │ ├── 13.tvflix.json │ │ ├── 2.feedful.json │ │ ├── 3.1.hawa.json │ │ ├── 3.123.sketch-to-ui.json │ │ ├── 3.2.pocketbase-nuxt.json │ │ ├── 3.formdata.json │ │ ├── 4.simpleonlinetools.json │ │ ├── 5.imbox.json │ │ ├── 6.bring-back-twitter.json │ │ ├── 6.onelink.json │ │ ├── 7.logspot.json │ │ ├── 8.iconbuddy.json │ │ └── 9.postperfect.json │ └── uses/ │ ├── Tableplus.json │ ├── apple-airpods-3.json │ ├── apple-iphone-12.json │ ├── apple-watch-series-7.json │ ├── bear.json │ ├── cleanshot.json │ ├── daily-objects-turf-desk-mat.json │ ├── dell-nero-dock.json │ ├── fake-plants.json │ ├── featherlite-helix-chair.json │ ├── gifski.json │ ├── httpie.json │ ├── ikea-headphone-stand.json │ ├── jbl-csum-microphone.json │ ├── keychron-k2.json │ ├── krisp.json │ ├── lenovo-monitor.json │ ├── logitech-mx-keys.json │ ├── logitech-mx-master-3s.json │ ├── macbook.json │ ├── miyoo-mini.json │ ├── monitorcontrol.json │ ├── purple-ark-sit-stand-desk.json │ ├── raycast.json │ ├── reminders-menubar.json │ ├── sony-playstation-4.json │ ├── sony-wh-1000xm4.json │ ├── texts.json │ ├── vscode.json │ └── zed.json ├── nuxt.config.ts ├── package.json ├── pages/ │ ├── articles/ │ │ ├── [slug].vue │ │ └── index.vue │ ├── bookmarks.vue │ ├── index.vue │ ├── lab.vue │ ├── projects.vue │ └── whats-in-my-bag.vue ├── tailwind.config.ts └── tsconfig.json
Condensed preview — 96 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (80K chars).
[
{
"path": ".gitignore",
"chars": 193,
"preview": "# Nuxt dev/build outputs\n.output\n.data\n.nuxt\n.nitro\n.cache\ndist\n\n# Node dependencies\nnode_modules\n\n# Logs\nlogs\n*.log\n\n# "
},
{
"path": ".npmrc",
"chars": 53,
"preview": "shamefully-hoist=true\nstrict-peer-dependencies=false\n"
},
{
"path": ".prettierrc",
"chars": 106,
"preview": "{\n \"trailingComma\": \"es5\",\n \"tabWidth\": 2,\n \"semi\": true,\n \"singleQuote\": false,\n \"printWidth\": 80\n}\n"
},
{
"path": ".vscode/settings.json",
"chars": 565,
"preview": "{\n \"files.associations\": {\n \"*.css\": \"tailwindcss\"\n },\n \"editor.quickSuggestions\": {\n \"strings\": "
},
{
"path": "README.md",
"chars": 1050,
"preview": "[<img src=\"https://github.com/user-attachments/assets/60e89805-26fd-4074-8ced-447fb148c7e6\">](http://supersaas.dev?ref=g"
},
{
"path": "app.config.ts",
"chars": 860,
"preview": "export default defineAppConfig({\n ui: {\n primary: \"teal\",\n gray: \"neutral\",\n formGroup: {\n help: \"text-xs"
},
{
"path": "app.vue",
"chars": 416,
"preview": "<template>\n <NuxtLoadingIndicator color=\"#14b8a6\" />\n <AppNavbar />\n <div class=\"h-32\"></div>\n <UContainer>\n <Nux"
},
{
"path": "components/App/ArticleCard.vue",
"chars": 1165,
"preview": "<template>\n <NuxtLink :to=\"article._path\" class=\"group\">\n <article>\n <time\n class=\"relative z-10 order-f"
},
{
"path": "components/App/Footer.vue",
"chars": 198,
"preview": "<template>\n <footer\n class=\"max-w-2xl mx-auto text-gray-400 dark:text-gray-600 text-sm text-center pb-8\"\n >\n <br"
},
{
"path": "components/App/Header.vue",
"chars": 418,
"preview": "<template>\n <div>\n <h1\n class=\"text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100\"\n >\n "
},
{
"path": "components/App/Navbar.vue",
"chars": 2362,
"preview": "<template>\n <div ref=\"headerRef\" :style=\"styles\" class=\"fixed top-0 w-full z-50\">\n <nav class=\"mx-auto px-4 sm:px-6 "
},
{
"path": "components/App/ProjectCard.vue",
"chars": 753,
"preview": "<template>\n <NuxtLink\n class=\"flex items-end gap-4 group p-2 -m-2 rounded-lg\"\n :to=\"project.url\"\n target=\"_bla"
},
{
"path": "components/App/ThemeToggle.vue",
"chars": 729,
"preview": "<script setup>\nconst colorMode = useColorMode();\n\nconst isDark = computed({\n get() {\n return colorMode.value === \"da"
},
{
"path": "components/App/UsesHeader.vue",
"chars": 468,
"preview": "<template>\n <li>\n <div\n class=\"relative after:-z-10 after:block after:h-[2px] after:absolute after:top-1/2 afte"
},
{
"path": "components/App/UsesItem.vue",
"chars": 421,
"preview": "<template>\n <li>\n <NuxtLink :to=\"item.url\" class=\"group\" target=\"_blank\" external>\n <p\n class=\"text-base"
},
{
"path": "components/Home/Divider.vue",
"chars": 308,
"preview": "<template>\n <div class=\"flex items-center justify-center gap-8 pointer-events-none\">\n <span class=\"h-0.5 w-0.5 bg-gr"
},
{
"path": "components/Home/FeaturedArticles.vue",
"chars": 750,
"preview": "<template>\n <div>\n <h2 class=\"uppercase text-xs font-semibold text-gray-400 mb-6\">\n RECENT ARTICLES\n </h2>\n "
},
{
"path": "components/Home/FeaturedProjects.vue",
"chars": 653,
"preview": "<template>\n <div>\n <h2 class=\"uppercase text-xs font-semibold text-gray-400 mb-6\">\n FEATURED PROEJCTS\n </h2>"
},
{
"path": "components/Home/Intro.vue",
"chars": 1547,
"preview": "<template>\n <div class=\"space-y-6\">\n <NuxtImg src=\"/avatar.png\" alt=\"Fayaz Ahmed\"\n class=\"ring-2 border ring-gr"
},
{
"path": "components/Home/Newsletter.vue",
"chars": 776,
"preview": "<template>\n <div>\n <div class=\"mb-6 flex items-center gap-3\">\n <div\n class=\"flex-none rounded-full p-1 t"
},
{
"path": "components/Home/SocialLinks.vue",
"chars": 1112,
"preview": "<template>\n <div>\n <h2 class=\"uppercase text-xs font-semibold text-gray-400 mb-4\">FIND ME ON</h2>\n <div class=\"sp"
},
{
"path": "components/content/AnimatedCounter.vue",
"chars": 1326,
"preview": "<template>\n <div class=\"px-4 py-8 flex items-center justify-center flex-col\">\n <span\n ref=\"target\"\n class="
},
{
"path": "components/content/CodeView.vue",
"chars": 108,
"preview": "<template>\n <div class=\"max-h-96 overflow-auto bg-gray-900 text-sm p-2\">\n <slot />\n </div>\n</template>\n"
},
{
"path": "components/content/Credit.vue",
"chars": 329,
"preview": "<template>\n <div class=\"text-sm p-4 bg-gray-100 dark:bg-gray-900 text-center\">\n This component was inspired by\n <"
},
{
"path": "components/content/Encryption.vue",
"chars": 1259,
"preview": "<template>\n <div\n class=\"bg-gray-100 dark:bg-gray-900 relative h-40 text-sm overflow-hidden\"\n @mousemove=\"handleO"
},
{
"path": "components/content/HackerButton.vue",
"chars": 1315,
"preview": "<template>\n <div class=\"px-4 py-8 flex items-center justify-center\">\n <button\n type=\"button\"\n class=\"round"
},
{
"path": "components/content/LabCard.vue",
"chars": 2638,
"preview": "<template>\n <div>\n <h2 class=\"text-sm font-semibold\">{{ title }}</h2>\n <p class=\"text-gray-500 text-sm\">\n {{"
},
{
"path": "components/content/Rocket.vue",
"chars": 4716,
"preview": "<template>\n <div\n class=\"py-12 relative overflow-hidden flex items-center justify-center w-full bg-gray-100 dark:bg-"
},
{
"path": "components/content/Shapes.vue",
"chars": 1640,
"preview": "<template>\n <div class=\"px-4 py-8 flex items-center justify-center flex-col\">\n <div class=\"flex w-full items-center "
},
{
"path": "components/content/TextRotator.vue",
"chars": 1288,
"preview": "<template>\n <div class=\"px-4 py-8 flex items-center justify-center\">\n <div\n class=\"font-extrabold text-lg [text"
},
{
"path": "content/articles/building-your-first-api-with-expressjs-a-beginners-guide.md",
"chars": 3183,
"preview": "---\ntitle: \"Building Your First API with Express.js: A Beginner's Guide\"\ndescription: \"A beginner-friendly guide to buil"
},
{
"path": "content/articles/how-to-convert-a-svg-to-png-using-canvas.md",
"chars": 3205,
"preview": "---\ntitle: \"How to convert a SVG to PNG using Canvas\"\ndescription: \"A simple way to convert a SVG to PNG using Canvas\"\np"
},
{
"path": "content/lab/1.text-rotator.md",
"chars": 1525,
"preview": "::LabCard{title=\"Text Rotator\" description=\"Rotate text with tailwindcss.\" :showUsageTab=false}\n\n#preview\n::TextRotator\n"
},
{
"path": "content/lab/2.hacker-button.md",
"chars": 1401,
"preview": "::LabCard{title=\"Hacker button\" description=\"Randomize text on click with vue\"}\n\n#preview\n::HackerButton{label=\"Submit F"
},
{
"path": "content/lab/3.animated-number-counter.md",
"chars": 1270,
"preview": "::LabCard{title=\"Animated number counter\" description=\"Animate numbers with a counter using tailwindcss and vue.\"}\n\n#pre"
},
{
"path": "content/lab/4.shapes-with-tailwindcss.md",
"chars": 1081,
"preview": "::LabCard{title=\"Shapes with Tailwindcss\" description=\"Some random shapes made with tailwindcss\" :showUsageTab=false}\n\n#"
},
{
"path": "content/lab/5.rocket.md",
"chars": 5324,
"preview": "::LabCard{title=\"Rocket\" description=\"A super fast rocket animation\" :showUsageTab=false :showCreditTab=true}\n\n#preview\n"
},
{
"path": "content/lab/6.encryption.md",
"chars": 1367,
"preview": "::LabCard{title=\"Encrypted text\" description=\"The encrypted text from evervault\" :showUsageTab=false :showCreditTab=true"
},
{
"path": "content/projects/1.feedbackjar.json",
"chars": 215,
"preview": "{\n \"name\": \"Feedbackjar\",\n \"url\": \"https://feedbackjar.app\",\n \"description\": \"Open source feedback, roadmaps and chan"
},
{
"path": "content/projects/10.fluenticons.json",
"chars": 202,
"preview": "{\n \"name\": \"Fluenticons\",\n \"url\": \"https://fluenticons.co\",\n \"description\": \"Microsoft fluenticons viewer\",\n \"thumbn"
},
{
"path": "content/projects/11.appydev.json",
"chars": 174,
"preview": "{\n \"name\": \"Appydev\",\n \"url\": \"https://appydev.co\",\n \"description\": \"Tools for designers and developers\",\n \"thumbnai"
},
{
"path": "content/projects/12.gitstars.json",
"chars": 147,
"preview": "{\n \"name\": \"Gitstars\",\n \"url\": \"\",\n \"description\": \"Better github trending page\",\n \"thumbnail\": \"/projects/gitstars."
},
{
"path": "content/projects/13.tvflix.json",
"chars": 187,
"preview": "{\n \"name\": \"Tvflix\",\n \"url\": \"https://tvflix.netlify.app\",\n \"description\": \"IPTV player with 100K+ tv stations in bro"
},
{
"path": "content/projects/2.feedful.json",
"chars": 176,
"preview": "{\n \"name\": \"Feedful\",\n \"url\": \"https://feedful.app\",\n \"description\": \"Modern news reader in tweetdeck style\",\n \"thum"
},
{
"path": "content/projects/3.1.hawa.json",
"chars": 218,
"preview": "{\n \"name\": \"Hawa\",\n \"url\": \"https://github.com/fayazara/hawa\",\n \"description\": \"A simple white noise app which sits i"
},
{
"path": "content/projects/3.123.sketch-to-ui.json",
"chars": 256,
"preview": "{\n \"name\": \"Sketch to UI\",\n \"url\": \"https://github.com/fayazara/sketch-to-ui\",\n \"description\": \"Covert bad hand drawn"
},
{
"path": "content/projects/3.2.pocketbase-nuxt.json",
"chars": 239,
"preview": "{\n \"name\": \"Pocketbase Nuxt\",\n \"url\": \"https://github.com/fayazara/pocketbase-nuxt\",\n \"description\": \"A starter templ"
},
{
"path": "content/projects/3.formdata.json",
"chars": 201,
"preview": "{\n \"name\": \"Formdata\",\n \"url\": \"https://formdata.cc\",\n \"description\": \"Relay form data to your email for free\",\n \"th"
},
{
"path": "content/projects/4.simpleonlinetools.json",
"chars": 224,
"preview": "{\n \"name\": \"Simple Online Tools\",\n \"url\": \"https://simpleonline.tools\",\n \"description\": \"Simple online tools for ever"
},
{
"path": "content/projects/5.imbox.json",
"chars": 195,
"preview": "{\n \"name\": \"Imbox\",\n \"url\": \"https://dub.sh/imbox\",\n \"description\": \"Temporary inbox as a chrome extension\",\n \"thumb"
},
{
"path": "content/projects/6.bring-back-twitter.json",
"chars": 276,
"preview": "{\n \"name\": \"Bring back the bird\",\n \"url\": \"https://github.com/fayazara/bring-back-twitter-bird\",\n \"description\": \"Chr"
},
{
"path": "content/projects/6.onelink.json",
"chars": 238,
"preview": "{\n \"name\": \"Onelink\",\n \"url\": \"https://onelink-nu.vercel.app/\",\n \"description\": \"An experimental link-in-bio tool, wh"
},
{
"path": "content/projects/7.logspot.json",
"chars": 235,
"preview": "{\n \"name\": \"Logspot\",\n \"url\": \"https://logspot.vercel.app/\",\n \"description\": \"A beautiful open source change log temp"
},
{
"path": "content/projects/8.iconbuddy.json",
"chars": 183,
"preview": "{\n \"name\": \"Iconbuddy\",\n \"url\": \"https://iconbuddy.app\",\n \"description\": \"200k+ open source icons in one place\",\n \"t"
},
{
"path": "content/projects/9.postperfect.json",
"chars": 176,
"preview": "{\n \"name\": \"Postperfect\",\n \"url\": \"https://postperfect.xyz\",\n \"description\": \"Chatgpt for your tweets\",\n \"thumbnail\""
},
{
"path": "content/uses/Tableplus.json",
"chars": 151,
"preview": "{\n \"name\": \"Tableplus\",\n \"description\": \"The only sane SQL client I've seen for Mac.\",\n \"url\": \"https://tableplus.com"
},
{
"path": "content/uses/apple-airpods-3.json",
"chars": 362,
"preview": "{\n \"name\": \"Apple airpods 3\",\n \"description\": \"Probably my most used item after my phone and laptop. I use them for ev"
},
{
"path": "content/uses/apple-iphone-12.json",
"chars": 311,
"preview": "{\n \"name\": \"Apple iPhone 12\",\n \"description\": \"I dont usually invest and upgrade my phone frequently, I used my last p"
},
{
"path": "content/uses/apple-watch-series-7.json",
"chars": 221,
"preview": "{\n \"name\": \"Apple watch series 7\",\n \"description\": \"I use this mostly to track my workouts\",\n \"url\": \"https://www.app"
},
{
"path": "content/uses/bear.json",
"chars": 215,
"preview": "{\n \"name\": \"Bear\",\n \"description\": \"The lack of code highlightin and markdown support in Apple notes, made me move to "
},
{
"path": "content/uses/cleanshot.json",
"chars": 374,
"preview": "{\n \"name\": \"Cleanshot\",\n \"description\": \"You might be wondering why would I pay for a screenshot taking tool. Well, it"
},
{
"path": "content/uses/daily-objects-turf-desk-mat.json",
"chars": 232,
"preview": "{\n \"name\": \"Daily Objects - Turf 2.0 Felt Desk Mat - Grey\",\n \"description\": \"This is clean, not too expensive and look"
},
{
"path": "content/uses/dell-nero-dock.json",
"chars": 451,
"preview": "{\n \"name\": \"Dell D6000 Usb 3.0 (3.1 Gen 1) Type-C Nero Port Black\",\n \"description\": \"My dock for my workstation, this "
},
{
"path": "content/uses/fake-plants.json",
"chars": 206,
"preview": "{\n \"name\": \"Fake plants\",\n \"description\": \"My desk was looking bland, so I bought some fake plants to make it look liv"
},
{
"path": "content/uses/featherlite-helix-chair.json",
"chars": 241,
"preview": "{\n \"name\": \"Featherlite Helix Chair\",\n \"description\": \"This is a really bad chair, but works for now 😅. Will save up t"
},
{
"path": "content/uses/gifski.json",
"chars": 136,
"preview": "{\n \"name\": \"Gifski\",\n \"description\": \"The best Video to GIF creator for Mac\",\n \"url\": \"https://gif.ski/\",\n \"category"
},
{
"path": "content/uses/httpie.json",
"chars": 187,
"preview": "{\n \"name\": \"HTTPie\",\n \"description\": \"An API testing tool with a beautiful UI, free of nonsense and unnecessary featur"
},
{
"path": "content/uses/ikea-headphone-stand.json",
"chars": 257,
"preview": "{\n \"name\": \"Ikea MÖJLIGHET headphone stand, black\",\n \"description\": \"This is super cheap, just ₹199, looks really clea"
},
{
"path": "content/uses/jbl-csum-microphone.json",
"chars": 250,
"preview": "{\n \"name\": \"JBL CSUM10 compact USB microphone\",\n \"description\": \"I don't use this. Got it from my previous companies W"
},
{
"path": "content/uses/keychron-k2.json",
"chars": 381,
"preview": "{\n \"name\": \"Keychron K2\",\n \"description\": \"I am little obsessed with keyboards and this is my current favorite. It's a"
},
{
"path": "content/uses/krisp.json",
"chars": 217,
"preview": "{\n \"name\": \"Krisp AI\",\n \"description\": \"Mute background noise during calls. Works so good, I have never gone a meeting"
},
{
"path": "content/uses/lenovo-monitor.json",
"chars": 297,
"preview": "{\n \"name\": \"Lenovo Q24 FHD monitor\",\n \"description\": \"I so want a new monitor, but this works for now and it's not bad"
},
{
"path": "content/uses/logitech-mx-keys.json",
"chars": 282,
"preview": "{\n \"name\": \"Logitech MX keys mini (Mac)\",\n \"description\": \"I use this as my keyboard, when I am at my desk\",\n \"url\": "
},
{
"path": "content/uses/logitech-mx-master-3s.json",
"chars": 378,
"preview": "{\n \"name\": \"Logitech MX Master 3s\",\n \"description\": \"This was a little expensive, but I needed a mouse that was really"
},
{
"path": "content/uses/macbook.json",
"chars": 232,
"preview": "{\n \"name\": \"Macbook pro 13 (2020)\",\n \"description\": \"The Intel i5 version. 16GB RAM, 512GB SSD\",\n \"url\": \"https://sup"
},
{
"path": "content/uses/miyoo-mini.json",
"chars": 229,
"preview": "{\n \"name\": \"Miyoo Mini v2\",\n \"description\": \"I really enjoy playing retro games. This is one of the best retro handhel"
},
{
"path": "content/uses/monitorcontrol.json",
"chars": 184,
"preview": "{\n \"name\": \"MonitorControl\",\n \"description\": \"Lets me control my external monitor brightness and volume directly\",\n \""
},
{
"path": "content/uses/purple-ark-sit-stand-desk.json",
"chars": 246,
"preview": "{\n \"name\": \"Purple ark sit/stand desk\",\n \"description\": \"I've always wanted a sit/stand desk and I love this one, it's"
},
{
"path": "content/uses/raycast.json",
"chars": 202,
"preview": "{\n \"name\": \"Raycast\",\n \"description\": \"My used application in my computer. I don' think I've ever relied on a software"
},
{
"path": "content/uses/reminders-menubar.json",
"chars": 177,
"preview": "{\n \"name\": \"Reminders Menubar\",\n \"description\": \"Apple reminders in your menubar\",\n \"url\": \"https://github.com/Damasc"
},
{
"path": "content/uses/sony-playstation-4.json",
"chars": 211,
"preview": "{\n \"name\": \"Sony Play station 4\",\n \"description\": \"I don't play it as much anymore, but I do play story based games so"
},
{
"path": "content/uses/sony-wh-1000xm4.json",
"chars": 376,
"preview": "{\n \"name\": \"SONY WH-1000XM4 Bluetooth Headset - Black\",\n \"description\": \"Essential when you work from home and want to"
},
{
"path": "content/uses/texts.json",
"chars": 171,
"preview": "{\n \"name\": \"Texts\",\n \"description\": \"Texts is so good, I stopped paying for Netflix and moved that to Texts.\",\n \"url\""
},
{
"path": "content/uses/vscode.json",
"chars": 216,
"preview": "{\n \"name\": \"VS Code\",\n \"description\": \"No brainer, my primary code editor. I use it for everything from writing code t"
},
{
"path": "content/uses/zed.json",
"chars": 214,
"preview": "{\n \"name\": \"Zed\",\n \"description\": \"A new code editor, I am waiting for them to add features like plugins, once it is d"
},
{
"path": "nuxt.config.ts",
"chars": 739,
"preview": "export default defineNuxtConfig({\n devtools: { enabled: true },\n modules: [\n \"@nuxt/ui\",\n \"nuxt-icon\",\n \"@nux"
},
{
"path": "package.json",
"chars": 709,
"preview": "{\n \"name\": \"nuxt-app\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"nuxt build\",\n \"dev\": \"nuxt dev\",\n \"generat"
},
{
"path": "pages/articles/[slug].vue",
"chars": 749,
"preview": "<template>\n <main class=\"min-h-screen\">\n <div\n class=\"prose dark:prose-invert prose-blockquote:not-italic prose"
},
{
"path": "pages/articles/index.vue",
"chars": 661,
"preview": "<template>\n <main class=\"min-h-screen\">\n <AppHeader class=\"mb-16\" title=\"Articles\" :description=\"description\" />\n "
},
{
"path": "pages/bookmarks.vue",
"chars": 3077,
"preview": "<template>\n <main class=\"min-h-screen\">\n <AppHeader class=\"mb-8\" title=\"Bookmarks\" :description=\"description\" />\n "
},
{
"path": "pages/index.vue",
"chars": 236,
"preview": "<template>\n <main class=\"min-h-screen\">\n <div class=\"space-y-24\">\n <HomeIntro />\n <HomeSocialLinks />\n "
},
{
"path": "pages/lab.vue",
"chars": 714,
"preview": "<template>\n <main class=\"min-h-screen\">\n <AppHeader class=\"mb-12\" title=\"Lab\" :description=\"description\" />\n <div"
},
{
"path": "pages/projects.vue",
"chars": 783,
"preview": "<template>\n <main class=\"min-h-screen\">\n <AppHeader class=\"mb-12\" title=\"Projects\" :description=\"description\" />\n "
},
{
"path": "pages/whats-in-my-bag.vue",
"chars": 1461,
"preview": "<template>\n <main class=\"min-h-screen\">\n <AppHeader\n class=\"mb-12\"\n title=\"What's in my bag?\"\n :descr"
},
{
"path": "tailwind.config.ts",
"chars": 1264,
"preview": "import type { Config } from \"tailwindcss\";\nmodule.exports = {\n content: [\n \"./components/**/*.{js,vue,ts}\",\n \"./l"
},
{
"path": "tsconfig.json",
"chars": 94,
"preview": "{\n // https://nuxt.com/docs/guide/concepts/typescript\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
}
]
About this extraction
This page contains the full source code of the fayazara/zooper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 96 files (67.9 KB), approximately 23.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.