new minimal retro theme, progress on parallax hero, refactor into semantic html,

add htmx and alpine, etc
This commit is contained in:
badblocks 2026-01-04 21:36:08 -08:00
parent 43e8dcff5e
commit 8d989ef36f
No known key found for this signature in database
25 changed files with 1520 additions and 179 deletions

3
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
interface Window {
Alpine: import("alpinejs").Alpine;
}

View file

@ -5,20 +5,38 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<link rel="stylesheet" type="text/css" href="/snes.min.css" />
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"
></script>
<link rel="stylesheet" type="text/css" href="/style.css" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=VT323&display=swap"
rel="stylesheet"
/>
<style>
div.container {
:root {
--min-body-width: 30ch;
--max-body-width: 120ch;
font-size: 16px;
font-family: "VT323", monospace;
font-weight: 400;
font-style: normal;
}
body {
display: grid;
grid-template-columns: 1fr;
grid-template-columns:
minmax(0.8rem, 1fr)
minmax(var(--min-body-width), var(--max-body-width))
minmax(0.8rem, 1fr);
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header"
"content"
"footer";
". header ."
". content ."
". footer .";
min-height: 100vh;
width: 100vw;
place-items: center;
width: 100%;
min-width: var(--min-body-width);
position: absolute;
top: 0;
left: 0;
@ -26,100 +44,64 @@
header {
grid-area: header;
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 2rem;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e5e5;
}
nav {
justify-self: end;
}
nav ul {
display: flex;
gap: 2rem;
list-style: none;
}
div.logo {
padding: 1rem 0;
header h1 {
text-transform: none;
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
nav li {
height: 3.5rem;
align-content: center;
}
nav li a {
display: inline-block;
line-height: 3.5rem;
nav li select {
margin: 0;
}
section.content {
main {
grid-area: content;
padding: 4rem 2rem;
}
footer {
grid-area: footer;
display: grid;
place-items: center;
padding: 2rem;
border-top: 1px solid #e5e5e5;
}
/* Tablet and up */
@media (min-width: 768px) {
}
/* Desktop */
@media (min-width: 1024px) {
div.container {
grid-template-columns: repeat(12, 1fr);
grid-template-areas:
"header header header header header header header header header header header header"
"content content content content content content content content content content content content"
"footer footer footer footer footer footer footer footer footer footer footer footer";
}
header {
padding: 1.5rem 4rem;
}
content {
padding-left: 4rem;
padding-right: 4rem;
}
}
</style>
<slot name="head" />
</head>
<body>
<div class="container">
<header>
<a href="/"><div class="logo">badblocks.dev</div></a>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/contact">Contact</a></li>
<li><a href="https://git.badblocks.dev">Git</a></li>
</ul>
</nav>
</header>
<section id="content" class="content">
<slot name="content" />
</section>
<footer>
<p>
&copy; <script is:inline>
document.write(new Date().getFullYear());
</script>
badblocks
</p>
</footer>
</div>
<body x-data="{ theme: 'winternight'}">
<header>
<h1><a href="/">badblocks.dev</a></h1>
<nav>
<ul>
<li>
<select id="theme" x-model="theme">
<option value="winternight">Winternight</option>
<option value="sunnyvale-forest">SunnyVale</option>
<option value="city-destroyed">Apocalypse</option>
</select>
</li>
<li><a href="/tools">Tools</a></li>
<li><a href="/contact">Contact</a></li>
<li><a href="https://git.badblocks.dev" target="_blank">Git</a></li>
</ul>
</nav>
</header>
<main id="content">
<slot name="content" />
</main>
<footer>
<p>
&copy; <script is:inline>
document.write(new Date().getFullYear());
</script>
badblocks
</p>
<p>Made from scratch with BAHA: Bun, Astro, Htmx, and Alpine!</p>
</footer>
</body>
</html>

View file

@ -1,24 +1,52 @@
---
import Layout from "../layouts/BaseLayout.astro";
export const prerender = false;
---
<Layout>
<title slot="head">Contact</title>
<style>
.contact {
grid-area: contact;
form {
display: grid;
place-items: center;
padding: 4rem 2rem;
background: #f8f9fa;
gap: 1rem;
width: 100%;
margin: 0 auto;
grid-template-areas:
"header header "
"name phone"
"msg msg"
" . submit";
}
label[for="name"] {
grid-area: name;
}
label[for="phone"] {
grid-area: phone;
}
label[for="msg"] {
grid-area: msg;
}
button#submit {
grid-area: submit;
}
</style>
<Fragment slot="content">
<section class="contact" id="contact">
<div>
<h2>Contact</h2>
<p>Contact information here</p>
</div>
</section>
<h2>Contact</h2>
<p>Use the below form to shoot me a quick text!</p>
<form x-data="{}">
<label for="name">
Name
<input type="text" id="name" placeholder="John Doe" />
</label>
<label for="phone">
Phone
<input type="text" id="phone" placeholder="555-555-5555" />
</label>
<label for="msg">
Msg
<textarea id="msg" placeholder="I think badblocks rocks!"></textarea>
</label>
<button id="submit" type="submit">Submit</button>
</form>
</Fragment>
</Layout>

View file

@ -6,11 +6,63 @@ import Layout from "../layouts/BaseLayout.astro";
<title slot="head">Home</title>
<style>
:root {
--scroll-speed: 10s;
--scroll-multiplier: 5;
--theme: winternight;
}
.parallax-theme-winternight {
--layer-0-speed: 6250s;
--layer-1-speed: 1250s;
--layer-2-speed: 500s;
--layer-3-speed: 250s;
--layer-4-speed: 80s;
--layer-0-url: url("/parallax/winternight/0-sky.png");
--layer-1-url: url("/parallax/winternight/1-backmountain.png");
--layer-2-url: url("/parallax/winternight/2-midmountain.png");
--layer-3-url: url("/parallax/winternight/3-midforest.png");
--layer-4-url: url("/parallax/winternight/4-frontfloor.png");
--parallax-width: calc(3800px * 10);
}
.parallax-theme-sunnyvale-forest {
--layer-0-speed: 2000s;
--layer-1-speed: 400s;
--layer-2-speed: 50s;
--layer-0-url: url("/parallax/sunnyvale-forest/0-clouds.png");
--layer-1-url: url("/parallax/sunnyvale-forest/1-mountains.png");
--layer-2-url: url("/parallax/sunnyvale-forest/2-trees.png");
--parallax-width: calc(160px * 200);
}
.parallax-theme-city-destroyed {
--layer-0-speed: 1000s;
--layer-1-speed: 275s;
--layer-2-speed: 250s;
--layer-3-speed: 250s;
--layer-4-speed: 250s;
--layer-5-speed: 100s;
--layer-0-url: url("/parallax/city-destroyed/0-sky.png");
--layer-1-url: url("/parallax/city-destroyed/1-buildingssmoke.png");
--layer-2-url: url("/parallax/city-destroyed/2-water.png");
--layer-3-url: url("/parallax/city-destroyed/3-buildings.png");
--layer-4-url: url("/parallax/city-destroyed/4-buildingreflection.png");
--layer-5-url: url("/parallax/city-destroyed/5-frontsmoke.png");
--parallax-width: calc(3800px * 10);
}
.parallax-theme-city-destroyed .layer.layer-1,
.parallax-theme-city-destroyed .layer.layer-3,
.parallax-theme-city-destroyed .layer.layer-4 {
background-repeat: repeat-x;
}
.parallax-theme-city-destroyed .layer.layer-4 {
background-position-y: 16rem;
}
.parallax-container {
position: relative;
height: 208px;
height: 30rem;
width: 100%;
overflow: hidden;
z-index: 0;
@ -21,28 +73,52 @@ import Layout from "../layouts/BaseLayout.astro";
top: 0;
left: 0;
height: 100%;
width: 1600vw; /* bg images are 160px, so use 16*vw for smooth scrolling */
width: var(--parallax-width);
background-repeat: round;
background-position: bottom;
background-size: contain;
}
.layer-back {
background-image: url("/bg-clouds.png");
.layer-0 {
background-image: var(--layer-0-url);
z-index: 1;
animation: scroll calc(var(--scroll-speed) * 42) linear infinite;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-0-speed))
linear infinite;
}
.layer-middle {
background-image: url("/bg-mountains.png");
.layer-1 {
background-image: var(--layer-1-url);
z-index: 2;
animation: scroll calc(var(--scroll-speed) * 32) linear infinite;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-1-speed))
linear infinite;
}
.layer-front {
background-image: url("/bg-trees.png");
.layer-2 {
background-image: var(--layer-2-url);
z-index: 3;
animation: scroll calc(var(--scroll-speed) * 11) linear infinite;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-2-speed))
linear infinite;
}
.layer-3 {
background-image: var(--layer-3-url);
z-index: 4;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-3-speed))
linear infinite;
}
.layer-4 {
background-image: var(--layer-4-url);
z-index: 5;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-4-speed))
linear infinite;
}
.layer-5 {
background-image: var(--layer-5-url);
z-index: 6;
animation: scroll calc(var(--scroll-multiplier) * var(--layer-5-speed))
linear infinite;
}
@keyframes scroll {
@ -53,42 +129,22 @@ import Layout from "../layouts/BaseLayout.astro";
transform: translateX(-50%);
}
}
section#hero {
position: relative;
padding: 2rem;
background-color: #f8f9fa;
z-index: -1;
}
div.hero {
position: relative;
top: calc(-4 * 0.9rem);
left: 12px;
width: calc(100% - 24px);
}
div.hero > h1 {
color: #fff;
margin-bottom: 16px;
}
div.hero > p {
font-size: 16px;
}
</style>
<Fragment slot="content">
<section id="hero">
<div class="parallax-container">
<div class="layer layer-back"></div>
<div class="layer layer-middle"></div>
<div class="layer layer-front"></div>
<article id="hero">
<div class="parallax-container" :class="'parallax-theme-' + theme">
<div class="layer layer-0"></div>
<div class="layer layer-1"></div>
<div class="layer layer-2"></div>
<div class="layer layer-3"></div>
<div class="layer layer-4"></div>
<div class="layer layer-5"></div>
</div>
<div class="hero">
<h1>Lorem Ipsum</h1>
<p>
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
consectetur, adipisci velit.
</p>
</div>
</section>
<h2>Lorem Ipsum</h2>
<p>
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
consectetur, adipisci velit.
</p>
</article>
</Fragment>
</Layout>

View file

@ -1,33 +0,0 @@
---
import Layout from "../layouts/BaseLayout.astro";
---
<Layout>
<title slot="head">Projects</title>
<style>
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
gap: 2rem;
max-width: 1200px;
margin: 2rem auto 0;
}
.project-card {
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
min-height: 250px;
}
</style>
<Fragment slot="content">
<section class="projects" id="projects">
<h2>Projects</h2>
<div class="projects-grid">
<div class="project-card">Project 1</div>
<div class="project-card">Project 2</div>
<div class="project-card">Project 3</div>
</div>
</section>
</Fragment>
</Layout>

78
src/pages/robots.txt.ts Normal file
View file

@ -0,0 +1,78 @@
import type { APIRoute } from "astro";
const getRobotsTxt = (sitemapURL: URL) => `\
# Block all known AI crawlers and assistants
# from using content for training AI models.
# Source: https://robotstxt.com/ai
User-Agent: GPTBot
User-Agent: ClaudeBot
User-Agent: Claude-User
User-Agent: Claude-SearchBot
User-Agent: CCBot
User-Agent: Google-Extended
User-Agent: Applebot-Extended
User-Agent: Facebookbot
User-Agent: Meta-ExternalAgent
User-Agent: Meta-ExternalFetcher
User-Agent: diffbot
User-Agent: PerplexityBot
User-Agent: PerplexityUser
User-Agent: Omgili
User-Agent: Omgilibot
User-Agent: webzio-extended
User-Agent: ImagesiftBot
User-Agent: Bytespider
User-Agent: TikTokSpider
User-Agent: Amazonbot
User-Agent: Youbot
User-Agent: SemrushBot-OCOB
User-Agent: Petalbot
User-Agent: VelenPublicWebCrawler
User-Agent: TurnitinBot
User-Agent: Timpibot
User-Agent: OAI-SearchBot
User-Agent: ICC-Crawler
User-Agent: AI2Bot
User-Agent: AI2Bot-Dolma
User-Agent: DataForSeoBot
User-Agent: AwarioBot
User-Agent: AwarioSmartBot
User-Agent: AwarioRssBot
User-Agent: Google-CloudVertexBot
User-Agent: PanguBot
User-Agent: Kangaroo Bot
User-Agent: Sentibot
User-Agent: img2dataset
User-Agent: Meltwater
User-Agent: Seekr
User-Agent: peer39_crawler
User-Agent: cohere-ai
User-Agent: cohere-training-data-crawler
User-Agent: DuckAssistBot
User-Agent: Scrapy
User-Agent: Cotoyogi
User-Agent: aiHitBot
User-Agent: Factset_spyderbot
User-Agent: FirecrawlAgent
Disallow: /
DisallowAITraining: /
# Block any non-specified AI crawlers (e.g., new
# or unknown bots) from using content for training
# AI models, while allowing the website to be
# indexed and accessed by bots. These directives
# are still experimental and may not be supported
# by all AI crawlers.
User-Agent: *
DisallowAITraining: /
Content-Usage: ai=n
Allow: /
Sitemap: ${sitemapURL.href}
`;
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL("sitemap-index.xml", site);
return new Response(getRobotsTxt(sitemapURL));
};

26
src/pages/tools.astro Normal file
View file

@ -0,0 +1,26 @@
---
import Layout from "../layouts/BaseLayout.astro";
---
<Layout>
<title slot="head">Tools</title>
<style>
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
gap: 2rem;
max-width: 1200px;
margin: 2rem auto 0;
}
</style>
<Fragment slot="content">
<section class="tools" id="tools">
<h2>Tools</h2>
<div class="tools-grid">
<div class="tool-card">Tool 1</div>
<div class="tool-card">Tool 2</div>
<div class="tool-card">Tool 3</div>
</div>
</section>
</Fragment>
</Layout>