Repository: markhorn-dev/astro-sphere
Branch: main
Commit: f7e3a40f20e1
Files: 68
Total size: 103.8 KB
Directory structure:
gitextract_txjs_ovg/
├── .github/
│ └── workflows/
│ └── stale.yaml
├── .gitignore
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── LICENSE
├── README.md
├── astro.config.mjs
├── package.json
├── public/
│ ├── js/
│ │ ├── animate.js
│ │ ├── bg.js
│ │ ├── copy.js
│ │ ├── scroll.js
│ │ └── theme.js
│ └── robots.txt
├── src/
│ ├── components/
│ │ ├── ArrowCard.tsx
│ │ ├── BaseHead.astro
│ │ ├── Container.astro
│ │ ├── Counter.tsx
│ │ ├── Drawer.astro
│ │ ├── Footer.astro
│ │ ├── Header.astro
│ │ ├── MeteorShower.astro
│ │ ├── Search.tsx
│ │ ├── SearchBar.tsx
│ │ ├── SearchCollection.tsx
│ │ ├── StackCard.astro
│ │ └── TwinklingStars.astro
│ ├── consts.ts
│ ├── content/
│ │ ├── blog/
│ │ │ ├── 01-astro-sphere-file-structure/
│ │ │ │ └── index.md
│ │ │ ├── 02-astro-sphere-getting-started/
│ │ │ │ └── index.md
│ │ │ ├── 03-astro-sphere-add-new-post-or-projects/
│ │ │ │ └── index.md
│ │ │ ├── 04-astro-sphere-writing-markdown/
│ │ │ │ └── index.md
│ │ │ ├── 05-astro-sphere-writing-mdx/
│ │ │ │ ├── MyComponent.astro
│ │ │ │ └── index.mdx
│ │ │ └── 06-astro-sphere-social-links/
│ │ │ └── index.md
│ │ ├── config.ts
│ │ ├── legal/
│ │ │ ├── privacy.md
│ │ │ └── terms.md
│ │ ├── projects/
│ │ │ ├── project-1/
│ │ │ │ └── index.md
│ │ │ ├── project-2/
│ │ │ │ └── index.md
│ │ │ ├── project-3/
│ │ │ │ └── index.md
│ │ │ └── project-4/
│ │ │ └── index.md
│ │ └── work/
│ │ ├── apple.md
│ │ ├── facebook.md
│ │ ├── google.md
│ │ └── mcdonalds.md
│ ├── env.d.ts
│ ├── layouts/
│ │ ├── ArticleBottomLayout.astro
│ │ ├── ArticleTopLayout.astro
│ │ ├── BottomLayout.astro
│ │ ├── PageLayout.astro
│ │ └── TopLayout.astro
│ ├── lib/
│ │ └── utils.ts
│ ├── pages/
│ │ ├── blog/
│ │ │ ├── [...slug].astro
│ │ │ └── index.astro
│ │ ├── index.astro
│ │ ├── legal/
│ │ │ └── [...slug].astro
│ │ ├── projects/
│ │ │ ├── [...slug].astro
│ │ │ └── index.astro
│ │ ├── robots.txt.ts
│ │ ├── rss.xml.ts
│ │ ├── search/
│ │ │ └── index.astro
│ │ └── work/
│ │ └── index.astro
│ ├── styles/
│ │ └── global.css
│ └── types.ts
├── tailwind.config.mjs
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/stale.yaml
================================================
name: Close inactive issues
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 10
days-before-issue-close: 10
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 10 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 10 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"[astro]": {
"editor.defaultFormatter": "astro-build.astro-vscode"
}
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Mark Horn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

Astro Sphere is a static, minimalist, lightweight, lightning fast portfolio and blog theme based on my personal website.
It is primarily Astro, Tailwind and Typescript, with a very small amount of SolidJS for stateful components.
## 🚀 Deploy your own
[](https://vercel.com/new/clone?repository-url=https://github.com/markhorn-dev/astro-sphere) [](https://app.netlify.com/start/deploy?repository=https://github.com/markhorn-dev/astro-sphere)
## 📋 Features
- ✅ 100/100 Lighthouse performance
- ✅ Responsive
- ✅ Accessible
- ✅ SEO-friendly
- ✅ Typesafe
- ✅ Minimal style
- ✅ Light/Dark Theme
- ✅ Animated UI
- ✅ Tailwind styling
- ✅ Auto generated sitemap
- ✅ Auto generated RSS Feed
- ✅ Markdown support
- ✅ MDX Support (components in your markdown)
- ✅ Searchable content (posts and projects)
- ✅ Code Blocks - copy to clipboard
## 💯 Lighthouse score

## 🕊️ Lightweight
All pages under 100kb (including fonts)
## ⚡︎ Fast
Rendered in ~40ms on localhost
## 📄 Configuration
The blog posts on the demo serve as the documentation and configuration.
## 💻 Commands
All commands are run from the root of the project, from a terminal:
Replace npm with your package manager of choice. `npm`, `pnpm`, `yarn`, `bun`, etc
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run dev:network` | Starts dev server on local network |
| `npm run sync` | Generates TypeScript types for all Astro modules.|
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run preview:network` | Starts preview server on local network |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
| `npm run lint` | Run ESLint |
| `npm run lint:fix` | Auto-fix ESLint issues |
## 🗺️ Roadmap
A few features I plan to implement
- ⬜ Article Pages - Table of Contents
- ⬜ Article Pages - Share on social media
## ✨ Acknowledgement
Theme inspired by [Paco Coursey](https://paco.me/), [Lee Robinson](https://leerob.io/) and [Hayden Bleasel](https://www.haydenbleasel.com/)
## 🏛️ License
MIT
# 1.0.1 Update
Added ability to run dev and preview on local network.
added npm run dev:network
added npm run preview:network
Added slightly more particle density in both light and dark mode.
Added subtle dark mode star and meteor animations.
Removed eslint config
================================================
FILE: astro.config.mjs
================================================
import { defineConfig } from "astro/config"
import mdx from "@astrojs/mdx"
import sitemap from "@astrojs/sitemap"
import tailwind from "@astrojs/tailwind"
import solidJs from "@astrojs/solid-js"
// https://astro.build/config
export default defineConfig({
site: "https://astro-sphere-demo.vercel.app",
integrations: [mdx(), sitemap(), solidJs(), tailwind({ applyBaseStyles: false })],
})
================================================
FILE: package.json
================================================
{
"name": "astro-sphere",
"type": "module",
"version": "1.0.0",
"scripts": {
"dev": "astro dev",
"dev:network": "astro dev --host",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"preview:network": "astro dev --host",
"astro": "astro",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@astrojs/check": "^0.5.6",
"@astrojs/mdx": "^2.1.1",
"@astrojs/rss": "^4.0.5",
"@astrojs/sitemap": "^3.1.1",
"@astrojs/solid-js": "^4.0.1",
"@astrojs/tailwind": "^5.1.0",
"@tailwindcss/typography": "^0.5.10",
"astro": "^4.4.13",
"clsx": "^2.1.0",
"fuse.js": "^7.0.0",
"sharp": "^0.33.2",
"solid-js": "^1.8.15",
"tailwind-merge": "^2.2.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3"
}
}
================================================
FILE: public/js/animate.js
================================================
function animate() {
const animateElements = document.querySelectorAll('.animate')
animateElements.forEach((element, index) => {
setTimeout(() => {
element.classList.add('show')
}, index * 150)
});
}
document.addEventListener("DOMContentLoaded", animate)
document.addEventListener("astro:after-swap", animate)
================================================
FILE: public/js/bg.js
================================================
function generateParticles(n) {
let value = `${getRandom(2560)}px ${getRandom(2560)}px #000`;
for (let i = 2; i <= n; i++) {
value += `, ${getRandom(2560)}px ${getRandom(2560)}px #000`;
}
return value;
}
function generateStars(n) {
let value = `${getRandom(2560)}px ${getRandom(2560)}px #fff`;
for (let i = 2; i <= n; i++) {
value += `, ${getRandom(2560)}px ${getRandom(2560)}px #fff`;
}
return value;
}
function getRandom(max) {
return Math.floor(Math.random() * max);
}
function initBG() {
const particlesSmall = generateParticles(1000);
const particlesMedium = generateParticles(500);
const particlesLarge = generateParticles(250);
const particles1 = document.getElementById('particles1');
const particles2 = document.getElementById('particles2');
const particles3 = document.getElementById('particles3');
if (particles1) {
particles1.style.cssText = `
width: 1px;
height: 1px;
border-radius: 50%;
box-shadow: ${particlesSmall};
animation: animStar 50s linear infinite;
`;
}
if (particles2) {
particles2.style.cssText = `
width: 1.5px;
height: 1.5px;
border-radius: 50%;
box-shadow: ${particlesMedium};
animation: animateParticle 100s linear infinite;
`;
}
if (particles3) {
particles3.style.cssText = `
width: 2px;
height: 2px;
border-radius: 50%;
box-shadow: ${particlesLarge};
animation: animateParticle 150s linear infinite;
`;
}
const starsSmall = generateStars(1000);
const starsMedium = generateStars(500);
const starsLarge = generateStars(250);
const stars1 = document.getElementById('stars1');
const stars2 = document.getElementById('stars2');
const stars3 = document.getElementById('stars3');
if (stars1) {
stars1.style.cssText = `
width: 1px;
height: 1px;
border-radius: 50%;
box-shadow: ${starsSmall};
`;
}
if (stars2) {
stars2.style.cssText = `
width: 1.5px;
height: 1.5px;
border-radius: 50%;
box-shadow: ${starsMedium};
`;
}
if (stars3) {
stars3.style.cssText = `
width: 2px;
height: 2px;
border-radius: 50%;
box-shadow: ${starsLarge};
`;
}
}
document.addEventListener('astro:after-swap', initBG);
initBG();
================================================
FILE: public/js/copy.js
================================================
const codeBlocks = document.querySelectorAll('pre:has(code)');
//add copy btn to every code block on the dom
codeBlocks.forEach((code) => {
//button icon
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute('href', '/copy.svg#empty');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.classList.add('copy-svg');
svg.appendChild(use);
//create button
const btn = document.createElement('button');
btn.appendChild(svg);
btn.classList.add('copy-btn');
btn.addEventListener('click', (e) => copyCode(e));
//container to fix copy button
const container = document.createElement('div');
container.classList.add('copy-cnt');
container.appendChild(btn);
//add to code block
code.classList.add('relative');
code.appendChild(container);
});
/**
* @param {MouseEvent} event
*/
function copyCode(event) {
let codeBlock = getChildByTagName(event.currentTarget.parentElement.parentElement, 'CODE')
navigator.clipboard.writeText(codeBlock.innerText)
const use = getChildByTagName(getChildByTagName(event.currentTarget, 'svg'), 'use');
use.setAttribute('href', '/copy.svg#filled')
setTimeout(() => {
if (use) {
use.setAttribute('href', '/copy.svg#empty')
}
}, 100);
}
function getChildByTagName(element, tagName) {
return Array.from(element.children).find((child) => child.tagName === tagName);
}
================================================
FILE: public/js/scroll.js
================================================
function onScroll() {
const header = document.getElementById("header")
if (window.scrollY > 0) {
header.classList.add("scrolled")
} else {
header.classList.remove("scrolled")
}
}
document.addEventListener("scroll", onScroll)
================================================
FILE: public/js/theme.js
================================================
function changeTheme() {
const element = document.documentElement
const theme = element.classList.contains("dark") ? "light" : "dark"
const css = document.createElement("style")
css.appendChild(
document.createTextNode(
`* {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}`,
),
)
document.head.appendChild(css)
if (theme === "dark") {
element.classList.add("dark")
} else {
element.classList.remove("dark")
}
window.getComputedStyle(css).opacity
document.head.removeChild(css)
localStorage.theme = theme
}
function preloadTheme() {
const theme = (() => {
const userTheme = localStorage.theme
if (userTheme === "light" || userTheme === "dark") {
return userTheme
} else {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
}
})()
const element = document.documentElement
if (theme === "dark") {
element.classList.add("dark")
} else {
element.classList.remove("dark")
}
localStorage.theme = theme
}
window.onload = () => {
function initializeThemeButtons() {
const headerThemeButton = document.getElementById("header-theme-button")
const drawerThemeButton = document.getElementById("drawer-theme-button")
headerThemeButton?.addEventListener("click", changeTheme)
drawerThemeButton?.addEventListener("click", changeTheme)
}
document.addEventListener("astro:after-swap", initializeThemeButtons)
initializeThemeButtons()
}
document.addEventListener("astro:after-swap", preloadTheme)
preloadTheme()
================================================
FILE: public/robots.txt
================================================
User-agent: *
Allow: /
Sitemap: http://localhost:4321/sitemap-index.xml
================================================
FILE: src/components/ArrowCard.tsx
================================================
import { formatDate, truncateText } from "@lib/utils"
import type { CollectionEntry } from "astro:content"
type Props = {
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">
pill?: boolean
}
export default function ArrowCard({ entry, pill }: Props) {
return (
{entry.data.tags.map((tag: string) => ( // this line has an error; Parameter 'tag' implicitly has an 'any' type.ts(7006)
Tags
{filter().size > 0 && ( )}Test
``` ```` Output ```htmlTest
``` ## List Types ### Ordered List #### Syntax ```markdown 1. First item 2. Second item 3. Third item ``` #### Output 1. First item 2. Second item 3. Third item ### Unordered List #### Syntax ```markdown - List item - Another item - And another item ``` #### Output - List item - Another item - And another item ### Nested list #### Syntax ```markdown - Fruit - Apple - Orange - Banana - Dairy - Milk - Cheese ``` #### Output - Fruit - Apple - Orange - Banana - Dairy - Milk - Cheese ## Other Elements — abbr, sub, sup, kbd, mark #### Syntax ```markdown GIF is a bitmap image format. H2O Xn + Yn = Zn Press CTRL+ALT+Delete to end the session. Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. ``` #### Output GIF is a bitmap image format. H2O Xn + Yn = Zn Press CTRL+ALT+Delete to end the session. Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. ================================================ FILE: src/content/blog/05-astro-sphere-writing-mdx/MyComponent.astro ================================================ --- type Props = { name: string } const { name } = Astro.props ---I am a software engineer, ui/ux designer, product planner, mentor, student, minimalist, eternal optimist, crypto enthusiast and sarcasm connoisseur.
I love to both build and break things. I am motivated by challenging projects with self-guided research and dynamic problem solving. My true passion is crafting creative front end designs with unique takes on color, typography and motion.
During my career
I have built products ranging from marketing and ecommerce websites to complex enterprise apps with focus on delivering fast, elegant code along with delightful user interfaces.
Now
I currently work as a software engineer at StreamlineFS, where I do product planning, design and development.
Recent posts
All postsWebsite build with
Recent projects
All projectsLet's Connect
Reach out to me via email or on social media.
Last updated: {formatDate(date)}