Merge remote-tracking branch 'astro-blog/master'

This commit is contained in:
log101 2024-05-07 12:03:51 +03:00
commit 1d7cf0fbc2
27 changed files with 7604 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# 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
# jetbrains setting folder
.idea/
TODO

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Ignore artifacts:
build
coverage
public

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

4
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"eslint.format.enable": true,
"prettier.configPath": ".prettierrc",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

54
README.md Normal file
View File

@ -0,0 +1,54 @@
# Astro Starter Kit: Basics
```sh
npm create astro@latest -- --template basics
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

6
astro.config.mjs Normal file
View File

@ -0,0 +1,6 @@
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
integrations: [],
});

6459
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "log101-blog",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.5.10",
"astro": "^4.7.0",
"typescript": "^5.4.5"
},
"devDependencies": {
"prettier": "3.2.5"
}
}

9
public/favicon.svg Normal file
View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

61
src/components/Card.astro Normal file
View File

@ -0,0 +1,61 @@
---
interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
display: flex;
padding: 1px;
background-color: #23262d;
background-image: none;
background-size: 400%;
border-radius: 7px;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: calc(1.5rem - 1px);
border-radius: 8px;
color: white;
background-color: #23262d;
opacity: 0.8;
}
h2 {
margin: 0;
font-size: 1.25rem;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
background-image: var(--accent-gradient);
}
.link-card:is(:hover, :focus-within) h2 {
color: rgb(var(--accent-light));
}
</style>

View File

@ -0,0 +1,8 @@
---
---
<div class="footer">
<p class="copyright">© 2024 Furkan Erdem</p>
<a class="about-me" href="#">Hakkımda</a>
</div>

View File

@ -0,0 +1,36 @@
---
---
<div class="header">
<ul class="contact-links undecorated-anchor">
<li><a href="#">Eposta</a></li>
<li><a href="#">Github</a></li>
<li><a href="#">LinkedIn</a></li>
<li><a href="#">CV</a></li>
</ul>
<div class="title-and-nav">
<div class="title-and-gol">
<h1 class="main-title">log101</h1>
<div id="board" class="board"></div>
</div>
<div>
<ul class="nav-links undecorated-anchor">
<li><a href="#">Teknik</a></li>
<li><a href="#">Fikir</a></li>
<li><a href="#">Babür'ün Serüvenleri</a></li>
<li><a href="#">Ansiklopedi</a></li>
</ul>
<hr />
</div>
</div>
</div>
<script>
import ConwaySimulator from "../scripts/conway-simulator";
let gol = new ConwaySimulator(10, 46, 10, 250);
document.getElementById("board")?.append(gol.canvas);
gol.start();
</script>

View File

@ -0,0 +1,20 @@
---
title: "Atatürk ve Demokratik Türkiye"
summary: Halil İnalcık'ın kaleminden Türkiye Cumhuriyet'nin kuruluş hikayesi ve Atatürk inkılaplarının toplumdaki akisleri.
category: Kitap İncelemesi
date: 2024-03-15
tags:
- kitap
- atatürk
- inkılap tarihi
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ut neque sollicitudin, pretium ipsum lacinia, mattis elit. Fusce in dolor suscipit, rutrum risus vehicula, feugiat ipsum. Sed sit amet enim cursus, efficitur eros eu, dapibus lectus. In nec risus eget nisl sodales bibendum nec sit amet nisl. Nulla a dolor finibus, commodo velit et, aliquam lacus. Nunc cursus ultrices tellus, ut volutpat mauris luctus vitae. Donec tristique sollicitudin est, sed iaculis odio eleifend non.
Fusce posuere non elit in vehicula. Proin facilisis dignissim bibendum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed posuere mi lectus, at eleifend mi tristique eu. Suspendisse potenti. Nam et eros velit.
Cras viverra sollicitudin neque et bibendum. Pellentesque ultrices mattis venenatis. Nam leo neque, tristique sed arcu vitae, semper rutrum justo. Ut porta orci nec consectetur bibendum. Etiam feugiat pretium lacus, non molestie lorem cursus sed. Nullam a vehicula turpis, nec tristique mauris. Cras euismod, lectus vitae ultricies elementum, ante dolor cursus lorem, at placerat nulla leo eget mauris. Aenean feugiat est ac sem auctor mollis. Maecenas eget interdum leo. Aenean tristique augue sapien, sit amet malesuada nisl molestie sed. Nullam volutpat pellentesque metus, ut mollis odio. Phasellus molestie iaculis lacinia.
Pellentesque vel massa a ipsum volutpat accumsan in vitae eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porttitor non ligula eget congue. Nunc dictum augue in eros mattis, ut varius risus maximus. Etiam augue tortor, rutrum ac quam vitae, iaculis fringilla lacus. Donec ullamcorper nunc non odio eleifend, vel pulvinar neque fermentum. Cras vel cursus metus. Vestibulum volutpat porta urna, aliquam mollis sem placerat nec. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent eu dictum odio. Nunc eleifend arcu vulputate, volutpat elit ullamcorper, feugiat sapien. Ut egestas aliquet faucibus. Fusce vitae lectus eu nunc mollis aliquet. Fusce cursus imperdiet auctor.
Vivamus urna arcu, bibendum vitae finibus in, fringilla ut nunc. Praesent fermentum velit non tortor porta, et commodo lectus blandit. Sed non mauris libero. Cras interdum, lorem a dignissim sagittis, diam lorem hendrerit lorem, sit amet porta sapien mauris sed augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin fringilla urna quis tellus hendrerit mattis. Nulla facilisi. In blandit sapien et lacus sollicitudin, ut elementum ex maximus. Integer efficitur libero eleifend luctus rutrum. Aenean a interdum leo, id blandit ante. Nulla velit dolor, euismod nec tempor ut, lobortis sed orci. Ut euismod tempor turpis ac maximus. Pellentesque odio lectus, malesuada a efficitur ac, placerat vel urna.

View File

@ -0,0 +1,18 @@
---
title: "5. Bölüm: Kaptan ile Mücadele"
summary: "Babür'ün önünde yalnızca tek bir engel kalmıştır: Komutan."
category: Öykü
date: 2024-03-03
tags:
- babür
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ut neque sollicitudin, pretium ipsum lacinia, mattis elit. Fusce in dolor suscipit, rutrum risus vehicula, feugiat ipsum. Sed sit amet enim cursus, efficitur eros eu, dapibus lectus. In nec risus eget nisl sodales bibendum nec sit amet nisl. Nulla a dolor finibus, commodo velit et, aliquam lacus. Nunc cursus ultrices tellus, ut volutpat mauris luctus vitae. Donec tristique sollicitudin est, sed iaculis odio eleifend non.
Fusce posuere non elit in vehicula. Proin facilisis dignissim bibendum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed posuere mi lectus, at eleifend mi tristique eu. Suspendisse potenti. Nam et eros velit.
Cras viverra sollicitudin neque et bibendum. Pellentesque ultrices mattis venenatis. Nam leo neque, tristique sed arcu vitae, semper rutrum justo. Ut porta orci nec consectetur bibendum. Etiam feugiat pretium lacus, non molestie lorem cursus sed. Nullam a vehicula turpis, nec tristique mauris. Cras euismod, lectus vitae ultricies elementum, ante dolor cursus lorem, at placerat nulla leo eget mauris. Aenean feugiat est ac sem auctor mollis. Maecenas eget interdum leo. Aenean tristique augue sapien, sit amet malesuada nisl molestie sed. Nullam volutpat pellentesque metus, ut mollis odio. Phasellus molestie iaculis lacinia.
Pellentesque vel massa a ipsum volutpat accumsan in vitae eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porttitor non ligula eget congue. Nunc dictum augue in eros mattis, ut varius risus maximus. Etiam augue tortor, rutrum ac quam vitae, iaculis fringilla lacus. Donec ullamcorper nunc non odio eleifend, vel pulvinar neque fermentum. Cras vel cursus metus. Vestibulum volutpat porta urna, aliquam mollis sem placerat nec. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent eu dictum odio. Nunc eleifend arcu vulputate, volutpat elit ullamcorper, feugiat sapien. Ut egestas aliquet faucibus. Fusce vitae lectus eu nunc mollis aliquet. Fusce cursus imperdiet auctor.
Vivamus urna arcu, bibendum vitae finibus in, fringilla ut nunc. Praesent fermentum velit non tortor porta, et commodo lectus blandit. Sed non mauris libero. Cras interdum, lorem a dignissim sagittis, diam lorem hendrerit lorem, sit amet porta sapien mauris sed augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin fringilla urna quis tellus hendrerit mattis. Nulla facilisi. In blandit sapien et lacus sollicitudin, ut elementum ex maximus. Integer efficitur libero eleifend luctus rutrum. Aenean a interdum leo, id blandit ante. Nulla velit dolor, euismod nec tempor ut, lobortis sed orci. Ut euismod tempor turpis ac maximus. Pellentesque odio lectus, malesuada a efficitur ac, placerat vel urna.

View File

@ -0,0 +1,19 @@
---
title: "Bir Komponentin Serüveni: Astro"
summary: Yazdığınız komponentlere ne olur? Bu yazıda Astro komponentlerinin serüvenlerine göz atıyoruz!
category: Teknik Yazı
date: 2024-04-01
tags:
- astro
- javascript
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ut neque sollicitudin, pretium ipsum lacinia, mattis elit. Fusce in dolor suscipit, rutrum risus vehicula, feugiat ipsum. Sed sit amet enim cursus, efficitur eros eu, dapibus lectus. In nec risus eget nisl sodales bibendum nec sit amet nisl. Nulla a dolor finibus, commodo velit et, aliquam lacus. Nunc cursus ultrices tellus, ut volutpat mauris luctus vitae. Donec tristique sollicitudin est, sed iaculis odio eleifend non.
Fusce posuere non elit in vehicula. Proin facilisis dignissim bibendum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed posuere mi lectus, at eleifend mi tristique eu. Suspendisse potenti. Nam et eros velit.
Cras viverra sollicitudin neque et bibendum. Pellentesque ultrices mattis venenatis. Nam leo neque, tristique sed arcu vitae, semper rutrum justo. Ut porta orci nec consectetur bibendum. Etiam feugiat pretium lacus, non molestie lorem cursus sed. Nullam a vehicula turpis, nec tristique mauris. Cras euismod, lectus vitae ultricies elementum, ante dolor cursus lorem, at placerat nulla leo eget mauris. Aenean feugiat est ac sem auctor mollis. Maecenas eget interdum leo. Aenean tristique augue sapien, sit amet malesuada nisl molestie sed. Nullam volutpat pellentesque metus, ut mollis odio. Phasellus molestie iaculis lacinia.
Pellentesque vel massa a ipsum volutpat accumsan in vitae eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porttitor non ligula eget congue. Nunc dictum augue in eros mattis, ut varius risus maximus. Etiam augue tortor, rutrum ac quam vitae, iaculis fringilla lacus. Donec ullamcorper nunc non odio eleifend, vel pulvinar neque fermentum. Cras vel cursus metus. Vestibulum volutpat porta urna, aliquam mollis sem placerat nec. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent eu dictum odio. Nunc eleifend arcu vulputate, volutpat elit ullamcorper, feugiat sapien. Ut egestas aliquet faucibus. Fusce vitae lectus eu nunc mollis aliquet. Fusce cursus imperdiet auctor.
Vivamus urna arcu, bibendum vitae finibus in, fringilla ut nunc. Praesent fermentum velit non tortor porta, et commodo lectus blandit. Sed non mauris libero. Cras interdum, lorem a dignissim sagittis, diam lorem hendrerit lorem, sit amet porta sapien mauris sed augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Proin fringilla urna quis tellus hendrerit mattis. Nulla facilisi. In blandit sapien et lacus sollicitudin, ut elementum ex maximus. Integer efficitur libero eleifend luctus rutrum. Aenean a interdum leo, id blandit ante. Nulla velit dolor, euismod nec tempor ut, lobortis sed orci. Ut euismod tempor turpis ac maximus. Pellentesque odio lectus, malesuada a efficitur ac, placerat vel urna.

16
src/content/config.ts Normal file
View File

@ -0,0 +1,16 @@
import { z, defineCollection } from "astro:content";
const blogCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
summary: z.string(),
date: z.date(),
category: z.enum(["Kitap İncelemesi", "Teknik Yazı", "Öykü"]),
}),
});
export const collections = {
blog: blogCollection,
};

2
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

41
src/layouts/Layout.astro Normal file
View File

@ -0,0 +1,41 @@
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style is:global>
:root {
--h1-desktop: 3.815rem;
--h2-desktop: 3.052rem;
--h3-desktop: 2.441rem;
--h4-desktop: 1.953rem;
--h5-desktop: 1.563rem;
--h6-desktop: 1.25rem;
--small-desktop: 0.8rem;
font-size: 20px;
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS",
sans-serif;
background-color: #f5fffa;
}
body {
display: flex;
justify-content: center;
}
</style>

38
src/pages/index.astro Normal file
View File

@ -0,0 +1,38 @@
---
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import Layout from "../layouts/Layout.astro";
import "../styles/header.css";
import "../styles/home.css";
import { getCollection } from "astro:content";
const allTeknikPosts = await getCollection("blog");
---
<Layout title="log101">
<div class="container">
<Header />
<div class="posts">
{
allTeknikPosts
.sort((p1, p2) => p2.data.date.getTime() - p1.data.date.getTime())
.map((p) => (
<div class="post">
<p class="post-category">{p.data.category}</p>
<h4 class="post-title">{p.data.title}</h4>
<p class="post-date">
{p.data.date.toLocaleDateString("tr-TR", {
day: "numeric",
month: "long",
year: "numeric",
})}
</p>
<p class="post-summary">{p.data.summary}</p>
</div>
))
}
</div>
<Footer />
</div>
</Layout>

View File

@ -0,0 +1,512 @@
import { heavyWeightSpaceshipCell } from "./utils";
/*
A wrapper for an HTML <canvas> based visualizatiuon of Conway's Game of Life.
*/
export default class ConwaySimulator {
/*
Create a new simulation. A simulation is comprised of a
2D data grid (rows-by-cols) of ConwayPixels, a canvas element,
and a canvas context.
*/
constructor(rows, cols, pixelSize, interRoundDelay) {
this.rows = rows;
this.cols = cols;
this.pixelSize = pixelSize;
this.interRoundDelay = interRoundDelay;
this.mouseIsDown = false;
this.paused = false;
this.intervalId = null;
// Make the grid
this.grid = [];
for (let i = 0; i < rows; i++) {
this.grid.push([]);
for (let j = 0; j < cols; j++) {
let alive = heavyWeightSpaceshipCell(j, i);
this.grid[i].push(new ConwayPixel(alive));
}
}
// Inform each pixel who it's neighbors are (performance optimization)
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
this.grid[i][j].neighbors = this.getNeighbors(i, j);
}
}
// Setup the canvas
let width = this.pixelSize * this.cols;
let height = this.pixelSize * this.rows;
this.canvas = document.createElement("canvas");
this.canvas.width = width;
this.canvas.height = height;
this.canvasCtx = this.canvas.getContext("2d", { alpha: true });
}
/*
Starts the simulation via setInterval if it's not running
*/
start() {
if (this.intervalId) {
return;
}
this.intervalId = setInterval(() => {
this.advanceRound();
this.repaint();
}, this.interRoundDelay);
}
/*
If the simulation is running, stop it using clearInterval
*/
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
/*
Return the neighbors of a particular grid location
*/
getNeighbors(row, col) {
let neighbors = [];
for (let i = row - 1; i <= row + 1; i++) {
for (let j = col - 1; j <= col + 1; j++) {
if (i === row && j === col) continue;
if (this.grid[i] && this.grid[i][j]) {
neighbors.push(this.grid[i][j]);
}
}
}
return neighbors;
}
/*
Update the grid according to the rules for each SimEntity
*/
advanceRound() {
if (this.mouseIsDown) return;
// First prepare each pixel (give it a next state)
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
this.grid[i][j].prepareUpdate();
}
}
// Then actually advance them, once all the new states are computed
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
this.grid[i][j].update();
}
}
}
/*
Optimized repaint that only updates pixels that have changed, and paints
in batches by color. Using force will repaint all pixels regardless of their
state/previousState/nextState, which is slower.
*/
repaint(force = false) {
if (this.mouseIsDown && !force) return;
// Canvas optimization -- it's faster to paint by color than placement.
let byColor = {};
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
let pixel = this.grid[i][j];
if (
!force &&
!pixel.forceRepaint &&
pixel.alive === pixel.previousState
) {
continue; // No need to repaint if the pixel didn't change
}
let color = pixel.alive ? pixel.lifeStyle : pixel.deathStyle;
if (byColor[color] === undefined) {
byColor[color] = [];
}
byColor[color].push([i, j]);
pixel.forceRepaint = false; // Once a pixel is painted, reset it's forced state
}
}
for (let color in byColor) {
this.canvasCtx.fillStyle = color;
for (let [row, col] of byColor[color]) {
this.canvasCtx.fillRect(
col * this.pixelSize,
row * this.pixelSize,
this.pixelSize,
this.pixelSize,
);
}
}
}
/*
Paint an individual pixel. This is not used by repaint because of a batch
optimziation. painting an individual pixel does take place when click events
happen.
*/
paintPixel(row, col) {
this.grid[row][col].setPaintStyles(this.canvasCtx);
this.canvasCtx.fillRect(
col * this.pixelSize,
row * this.pixelSize,
this.pixelSize,
this.pixelSize,
);
}
/* =============
Visualizatiuon Modifiers
================ */
/*
Give each entity in the grid an alive style such that when all pixels are alive
the grid would be a rainbow gradient.
*/
setRainbowScheme() {
let rows = this.grid.length;
let cols = this.grid[0].length;
let diagonalLength = Math.sqrt(
this.rows * this.rows + this.cols * this.cols,
);
let hueIncrement = 360 / diagonalLength;
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
let h = Math.floor(Math.sqrt(i * i + j * j) * hueIncrement);
let px = this.grid[i][j];
px.lifeStyle = `hsl(${h}, 100%, 60%)`;
px.deathStyle = `#000000`;
px.forceRepaint = true;
}
}
}
/*
Give each entity in the specified area of the grid an alive style
such that when all pixels are alive the area would be a rainbow gradient.
*/
setRainbowSchemeWithin(startRow, stopRow, startCol, stopCol) {
let rows = stopRow - startRow;
let cols = stopCol - startCol;
let diagonalLength = Math.sqrt(rows * rows + cols * cols);
let hueIncrement = 360 / diagonalLength;
for (let i = startRow; i < stopRow; i++) {
for (let j = startCol; j < stopCol; j++) {
let h = Math.floor(Math.sqrt(i * i + j * j) * hueIncrement);
let px = this.grid[i][j];
px.lifeStyle = `hsl(${h}, 100%, 60%)`;
px.deathStyle = `#000000`;
px.forceRepaint = true;
}
}
}
/*
set colors to the provided parameters
*/
setPixelColors(lifeStyle, deathStyle) {
this.grid.forEach((row) => {
row.forEach((entity) => {
entity.lifeStyle = lifeStyle;
entity.deathStyle = deathStyle;
entity.forceRepaint = true;
});
});
}
/*
Give the board random semi-complementary colors.
*/
setRandomPixelColors() {
let baseHue = randomInteger(1, 360);
let complementaryHue = baseHue + (randomInteger(90, 270) % 360);
this.setPixelColors(
`hsl(${baseHue}, 100%, 60%)`,
`hsl(${complementaryHue}, 100%, 60%)`,
);
}
/*
Given a bounding box, apply the currently selected rules to ONLY the
pixels within the provided box.
*/
applyColorsWithin(
rowStart,
rowStop,
colStart,
colStop,
lifeStyle,
deathStyle,
) {
for (let i = rowStart; i < rowStop; i++) {
for (let j = colStart; j < colStop; j++) {
let pixel = this.grid[i][j];
pixel.lifeStyle = lifeStyle;
pixel.deathStyle = deathStyle;
pixel.forceRepaint = true;
}
}
}
/*
Give a sopecific area of the board random semi-complementary colors.
*/
applyRandomColorsWithin(rowStart, rowStop, colStart, colStop) {
let baseHue = randomInteger(1, 360);
let complementaryHue = baseHue + (randomInteger(90, 270) % 360);
this.applyColorsWithin(
rowStart,
rowStop,
colStart,
`hsl(${baseHue}, 100%, 60%)`,
`hsl(${complementaryHue}, 100%, 60%)`,
);
}
/*
Set all the pixels to alive=false
*/
resetLife(chanceOfLife) {
this.grid.forEach((row) => {
row.forEach((pixel) => {
pixel.previousState = pixel.alive;
pixel.alive = Math.random() < chanceOfLife;
});
});
this.repaint();
}
/*
Given a bounding box, apply the currently selected rules to ONLY the
pixels within the provided box.
*/
resetLifeWithin(rowStart, rowStop, colStart, colStop, chanceOfLife = 0.1) {
for (let i = rowStart; i < rowStop; i++) {
for (let j = colStart; j < colStop; j++) {
let pixel = this.grid[i][j];
if (pixel) {
pixel.previousState = pixel.alive;
pixel.alive = Math.random() < chanceOfLife;
}
}
}
this.repaint();
}
/*
Update the rules for all the pixels
*/
setRules(underpopulation, overpopulation, reproductionMin, reproductionMax) {
this.grid.forEach((row) => {
row.forEach((pixel) => {
pixel.underpopulation = underpopulation;
pixel.overpopulation = overpopulation;
pixel.reproductionMin = reproductionMin;
pixel.reproductionMax = reproductionMax;
});
});
}
/*
Swap life and death styles across the center of the grid.
*/
setYinYangMode() {
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols / 2; j++) {
let t = this.grid[i][j].lifeStyle;
this.grid[i][j].lifeStyle = this.grid[i][j].deathStyle;
this.grid[i][j].deathStyle = t;
}
}
this.repaint(true);
}
/*
Given a bounding box, apply the currently selected rules to ONLY the
pixels within the provided box.
*/
setRulesWithin(
rowStart,
rowStop,
colStart,
colStop,
underpopulation,
overpopulation,
reproductionMin,
reproductionMax,
) {
for (let i = rowStart; i < rowStop; i++) {
for (let j = colStart; j < colStop; j++) {
let pixel = this.grid[i][j];
pixel.underpopulation = underpopulation;
pixel.overpopulation = overpopulation;
pixel.reproductionMin = reproductionMin;
pixel.reproductionMax = reproductionMax;
pixel.forceRepaint = true;
}
}
}
/*
The grid has click, and click-and-drag functionality. Entities define their
own behavior when clicked, and this function ensures the proper entity is
updated when it is clicked (or dragged-over)
*/
registerMouseListeners() {
bindMultipleEventListener(this.canvas, ["mousemove", "touchmove"], (e) => {
e.preventDefault();
if (this.mouseIsDown) {
let x, y;
if (e.touches) {
let rect = e.target.getBoundingClientRect();
x = Math.floor((e.touches[0].pageX - rect.left) / this.pixelSize);
y = Math.floor((e.touches[0].pageY - rect.top) / this.pixelSize);
} else {
x = Math.floor(e.offsetX / this.pixelSize);
y = Math.floor(e.offsetY / this.pixelSize);
}
this.grid[y][x].handleClick();
this.paintPixel(y, x);
}
});
// Capture mouse state for click and drag features
bindMultipleEventListener(this.canvas, ["mousedown", "touchstart"], (e) => {
e.preventDefault();
let rect = e.target.getBoundingClientRect();
let x, y;
if (e.touches) {
let rect = e.target.getBoundingClientRect();
x = Math.floor((e.touches[0].pageX - rect.left) / this.pixelSize);
y = Math.floor((e.touches[0].pageY - rect.top) / this.pixelSize);
} else {
x = Math.floor(e.offsetX / this.pixelSize);
y = Math.floor(e.offsetY / this.pixelSize);
}
this.grid[y][x].handleClick();
this.paintPixel(y, x);
this.mouseIsDown = true;
});
bindMultipleEventListener(this.canvas, ["mouseup", "touchend"], (e) => {
e.preventDefault();
this.mouseIsDown = false;
});
}
}
/*
A single pixel within a greater ConwaySimulator. Each ConwayPixel has it's own rules for evolution,
and for performance reason's maintains a list of it's neighbors inside of it's simulator.
This class is intended as an internal class, and is not exported. Manipulation of individual
ConwayPixels outside of the ConwaySimulator class is not advised.
*/
class ConwayPixel {
/*
Constuct a default ConwayPixel, which follows the original Game of Life rules.
*/
constructor(alive) {
this.alive = alive;
this.lifeStyle = "#000000";
this.deathStyle = "#F5FFFA";
this.underpopulation = 2;
this.overpopulation = 3;
this.reproductionMin = 3;
this.reproductionMax = 3;
// Experimental improvement...
this.neighbors = [];
this.nextState = null;
this.previousState = null;
this.forceRepaint = true;
// Reproduction min cannot be more than reproduction max
if (this.reproductionMax < this.reproductionMin) {
this.reproductionMin = this.reproductionMax;
}
}
/*
In order to process whole rounds at a time, update returns
a replacement entity, it does not edit the entity in place.
Following the rules for conway's game of life:
Any live cell with fewer than two live neighbors dies,
as if caused by underpopulation.
Any live cell with two or three live neighbors lives on
to the next generation.
Any live cell with more than three live neighbors dies,
as if by overpopulation.
Any dead cell with exactly three live neighbors becomes
a live cell, as if by reproduction.
*/
prepareUpdate() {
let sum = 0;
let nextState = this.alive;
for (let n of this.neighbors) {
if (n.alive && n !== this) sum++;
}
if (nextState && sum < this.underpopulation) {
nextState = false;
} else if (nextState && sum > this.overpopulation) {
nextState = false;
} else if (
!nextState &&
sum >= this.reproductionMin &&
sum <= this.reproductionMax
) {
nextState = true;
}
this.nextState = nextState;
}
/*
Advance this pixel to it's nextState.
*/
update() {
this.previousState = this.alive;
this.alive = this.nextState;
this.nextState = null;
}
/*
The calling context infers that a click HAS occured, this is not a mouse;
this is not an event listener.
*/
handleClick() {
this.alive = true;
}
/*
Provided with a canvas context, paint ourselves!
*/
setPaintStyles(canvasCtx) {
canvasCtx.fillStyle = this.alive ? this.lifeStyle : this.deathStyle;
}
}

100
src/scripts/gol.js Normal file
View File

@ -0,0 +1,100 @@
const boardElement = document.getElementById('board');
let boardWidth = 100; // Width of the board
const boardHeight = 7; // Height of the board
const currentGrid = []; // Current state of the grid
const nextGrid = []; // Next state of the grid
let intervalId; // ID for the setInterval
const cells = ['30', '20', '51', '01', '62', '63', '03', '64', '54', '44', '34', '24', '14']
const heavyWeightSpaceshipCell = (x, y) => {
const coor = `${x}${y}`
return cells.includes(coor)
}
function initializeBoard() {
for (let y = 0; y < boardHeight; y++) {
currentGrid[y] = [];
nextGrid[y] = [];
for (let x = 0; x < boardWidth; x++) {
let cell = document.createElement('div');
cell.classList.add('cell');
boardElement.appendChild(cell);
if (heavyWeightSpaceshipCell(x, y)) {
cell.classList.add('alive')
currentGrid[y][x] = 1;
nextGrid[y][x] = 0;
} else {
currentGrid[y][x] = 0;
nextGrid[y][x] = 0;
}
}
}
}
function computeNextGeneration() {
for (let y = 0; y < boardHeight; y++) {
for (let x = 0; x < boardWidth; x++) {
const aliveNeighbors = countAliveNeighbors(y, x);
const cell = currentGrid[y][x];
nextGrid[y][x] = (cell === 1 && (aliveNeighbors === 2 || aliveNeighbors === 3)) || (cell === 0 && aliveNeighbors === 3) ? 1 : 0;
}
}
// Apply changes and minimize DOM updates
for (let y = 0; y < boardHeight; y++) {
for (let x = 0; x < boardWidth; x++) {
if (currentGrid[y][x] !== nextGrid[y][x]) {
const cellElement = boardElement.children[y * boardWidth + x];
cellElement.classList.toggle('alive', nextGrid[y][x] === 1);
}
currentGrid[y][x] = nextGrid[y][x];
}
}
}
function countAliveNeighbors(y, x) {
let count = 0;
for (let yOffset = -1; yOffset <= 1; yOffset++) {
for (let xOffset = -1; xOffset <= 1; xOffset++) {
if (yOffset === 0 && xOffset === 0) continue;
const newY = (y + yOffset + boardHeight) % boardHeight;
const newX = (x + xOffset + boardWidth) % boardWidth;
count += currentGrid[newY][newX];
}
}
return count;
}
function startGame() {
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(computeNextGeneration, 100);
}
function changeWidth() {
if (intervalId) clearInterval(intervalId); // stop the game
document.querySelectorAll(".cell").forEach(el => el.remove()); // remove the cells
var r = document.querySelector(':root')
boardWidth += 50;
boardWidth %= 200;
r.style.setProperty('--board-width', boardWidth)
initializeBoard()
}
const startButton = document.getElementById('startButton')
startButton.onclick = startGame
const changeWidthButton = document.getElementById('changeWidthButton')
changeWidthButton.onclick = changeWidth
window.onresize = () => {
console.log(document.getElementById('board').clientWidth)
}
initializeBoard();

6
src/scripts/utils.js Normal file
View File

@ -0,0 +1,6 @@
const cells = ['30', '20', '51', '01', '62', '63', '03', '64', '54', '44', '34', '24', '14']
export const heavyWeightSpaceshipCell = (x, y) => {
const coor = `${x}${y}`
return cells.includes(coor)
}

87
src/styles/header.css Normal file
View File

@ -0,0 +1,87 @@
.container {
width: 750px;
display: flex;
flex-direction: column;
gap: 24px;
margin: 24px 0px;
}
.header {
display: flex;
flex-direction: column;
justify-content: start;
gap: 6px;
}
.contact-links {
display: flex;
flex-direction: row;
align-items: center;
list-style-type: none;
padding: 0;
margin: 0;
gap: 12px;
}
.contact-links a {
color: grey;
}
.undecorated-anchor a {
text-decoration: none;
}
.nav-links {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
list-style-type: none;
padding: 0;
margin: 0;
gap: 12px;
}
.nav-links a {
font-size: var(--h6-desktop);
color: inherit;
}
.title-and-nav {
display: flex;
flex-direction: column;
gap: 8px;
}
.title-and-nav hr {
margin-bottom: 0;
}
.title-and-gol {
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
height: 110px;
}
.main-title {
font-family: "Courier New", Courier, monospace;
font-size: var(--h1-desktop);
max-width: 250;
margin: 0;
padding: 0;
padding-bottom: 8px;
}
.footer {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
color: gray;
}
.footer a {
color: inherit;
}

36
src/styles/home.css Normal file
View File

@ -0,0 +1,36 @@
.posts {
display: flex;
flex-direction: column;
gap: 48px;
}
.post {
display: flex;
flex-direction: column;
gap: 8px;
}
.post-category {
margin: 0;
padding: 0;
color: #2f4f4f;
letter-spacing: 0.05em;
}
.post-title {
font-size: var(--h4-desktop);
font-weight: normal;
margin: 0;
padding: 0;
}
.post-summary {
margin: 0;
padding: 0;
}
.post-date {
margin: 0;
padding: 0;
color: gray;
}

3
tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/strict"
}