Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
4dae19d
Boilerplate for Book setup
FadiShawki Jan 20, 2026
1e0c0f3
Boilerplate for Book setup
FadiShawki Jan 20, 2026
4edebe1
Finalize boilerplate of book setup
FadiShawki Jan 21, 2026
62f4421
Add to homepage
FadiShawki Jan 21, 2026
557ec62
Add to homepage
FadiShawki Jan 21, 2026
d66f4fb
Add to homepage
FadiShawki Jan 21, 2026
2503e66
Fix padding
FadiShawki Jan 21, 2026
b344ea2
Merge branch 'main' into ethers-almanac
FadiShawki Jan 22, 2026
a8907ae
Make text editable and a start to the first section
FadiShawki Jan 23, 2026
216c803
Work on the Almanac, and download preview
FadiShawki Jan 25, 2026
539da04
Introduction
FadiShawki Jan 26, 2026
644ef4d
Some additional examples
FadiShawki Jan 27, 2026
04a9752
Some sections here and there
FadiShawki Jan 28, 2026
f60a3e3
Some sections here and there
FadiShawki Jan 29, 2026
1dba3eb
Some sections here and there
FadiShawki Jan 29, 2026
7cb761f
Processing notes
FadiShawki Jan 30, 2026
c54f61d
Some sections here and there
FadiShawki Jan 31, 2026
8affc55
Thoughts on UI equivalent of the website
FadiShawki Feb 1, 2026
bbad98a
Processing notes
FadiShawki Feb 1, 2026
518c0c7
Assignment
FadiShawki Feb 2, 2026
7be4dee
Assignment
FadiShawki Feb 2, 2026
ce748c3
Small change to punctuation
FadiShawki Feb 3, 2026
3db8e09
Small change to punctuation
FadiShawki Feb 4, 2026
8805570
Small change to punctuation
FadiShawki Feb 5, 2026
c8bb0dc
Small change to punctuation
FadiShawki Feb 6, 2026
6ba5935
Small change to punctuation
FadiShawki Feb 7, 2026
24d5ffe
Fix setParams
FadiShawki Feb 8, 2026
5283c0e
First iteration of search function
FadiShawki Feb 8, 2026
5a7b752
Finalize search function
FadiShawki Feb 8, 2026
b7cad04
Finalize search function
FadiShawki Feb 8, 2026
0ae7bbb
Add generate .jpeg for the book
FadiShawki Feb 8, 2026
704eda7
Partial fix for .pdf rendering
FadiShawki Feb 8, 2026
569cf5e
Setup download through github releases
FadiShawki Feb 12, 2026
0f52179
Enum syntax
FadiShawki Feb 21, 2026
496e4da
Notes
FadiShawki Feb 21, 2026
a7ee8b7
Notes
FadiShawki Feb 26, 2026
c1673fa
Create section for program types
FadiShawki Feb 27, 2026
8465771
Notes
FadiShawki Mar 3, 2026
929cc67
Some quality of life improvements
FadiShawki Mar 4, 2026
cf66a6c
Notes
FadiShawki Mar 8, 2026
8004190
Merge branch 'main' into ethers-almanac
FadiShawki Mar 12, 2026
22c365f
Scroll fix
FadiShawki Mar 13, 2026
ec2bc52
Merge branch 'main' into ethers-almanac
FadiShawki May 20, 2026
e6bcbe7
Move to post
FadiShawki May 20, 2026
7b48bba
Fix gutter
FadiShawki May 20, 2026
8ee4361
Continue with 2.1 Superposing variables
FadiShawki May 20, 2026
0a05a87
Continue with the almanac 2.2, and reorient some sections
FadiShawki May 21, 2026
6f3b0c6
Working on a graph
FadiShawki May 22, 2026
01ac426
Move the generated code; sketch from ray to orbitmines.com. We'll mov…
FadiShawki May 27, 2026
306df9f
Minor fixes
FadiShawki May 27, 2026
4582e41
Move the generated code; sketch from ray to orbitmines.com. We'll mov…
FadiShawki May 27, 2026
a65a4b0
Move some stuff around, fix some things like the search function disp…
FadiShawki May 28, 2026
e452ef5
refactor profile
FadiShawki May 28, 2026
1e59afd
Delete blueprintjs as a dependency, upgrade to react 19, switch to Ne…
FadiShawki May 30, 2026
c373fda
Replace react-router-dom with nextjs
FadiShawki May 30, 2026
96d3300
Remove node polyfill
FadiShawki May 30, 2026
e3cc436
Switch to turbopack
FadiShawki May 30, 2026
e81eb26
Fix
FadiShawki May 30, 2026
21d72a0
Fixes to the next.js setup
FadiShawki May 31, 2026
6d893c4
run dev instead of start
FadiShawki Jun 1, 2026
703c725
Merge branch 'main' into ethers-almanac
FadiShawki Jun 1, 2026
79e14d0
Merge
FadiShawki Jun 1, 2026
4e30b57
Fix routes
FadiShawki Jun 1, 2026
2fffd9e
Production fixes
FadiShawki Jun 1, 2026
bdfd21c
Remove react helmet as a dependency, use next.js infra
FadiShawki Jun 1, 2026
f1327f3
Fix more next.js setup
FadiShawki Jun 1, 2026
ce5f1ae
prerender @fadi-shawki
FadiShawki Jun 1, 2026
340496f
Expand introduction with a first sketch of the mission statement
FadiShawki Jun 4, 2026
09ad711
Alamanac.png
FadiShawki Jun 6, 2026
bc63497
Alamanac.png
FadiShawki Jun 6, 2026
e99c50b
Fixes and quality of life
FadiShawki Jun 7, 2026
9878b5e
Fix button
FadiShawki Jun 7, 2026
6011919
Cleanup
FadiShawki Jun 10, 2026
cbd48ae
Second attempt at a mission statement
FadiShawki Jun 10, 2026
b5c74bb
Second attempt at a mission statement
FadiShawki Jun 10, 2026
4a21ee7
Add to A. The Ray Programming Language
FadiShawki Jun 11, 2026
b8115d3
For Beginners
FadiShawki Jun 11, 2026
34818d8
Minor edits
FadiShawki Jun 12, 2026
c3d2495
Quality of life fixes
FadiShawki Jun 14, 2026
ce5caa4
Fix scrollbars for graph
FadiShawki Jun 14, 2026
379c6db
Add paragraph to introduction on forwards-compatibility
FadiShawki Jun 18, 2026
6efc73c
Syntax changes
FadiShawki Jun 22, 2026
8313f8f
small changes to punctuation
FadiShawki Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
./external

# orbitmines.com
orbitmines.com/.next
orbitmines.com/node_modules
orbitmines.com/build
orbitmines.com/.next

# Environment
**/.idea
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ git clone git@github.com:orbitmines/orbitmines.com.git # Website to the internet
npm install
```
- ```
npm start
npm run dev
```

---
Expand Down
1 change: 1 addition & 0 deletions orbitmines.com.ray/Almanac.ray
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Almanac = Book("almanac")
8 changes: 8 additions & 0 deletions orbitmines.com.ray/Article.ray
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Article (name: String) < name

class Book < Article

class Profile < Article

class Author

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Article("towards-a-universal-language")
35 changes: 35 additions & 0 deletions orbitmines.com.ray/index.ray
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
orbitmines.com() if &entrypoint

class orbitmines.com < Interface
{ url } = Browser

url.path.match
⊢"/profiles/", x: Profile$, "/"? =>
⊢"/archive/", x: Article$, "/"? =>
"/almanac" => Almanac()
"/thumbnail" =>

⊢"/papers/", x => url.path = "/archive/", x

* => Index()

class Index
articles: Article[] =
"towards-a-universal-language",
"2024-02-orbitmines-as-a-game-project",
"on-orbits-equivalence-and-inconsistencies",
"on-intelligibility"

Center
100% * Center{width <= 1240px}(
End(DownloadButton(), LoginButton()).map(& px 5)
)
100% * Center Image{width <= 400px}(./lib/organizations/orbitmines/logo.png, width: 90%)
100% * Center
(xl 4/12 lg 6/12 md 8/12 sm 10/12 xs 12/12) * Center "Once a Minecraft server, now the building of a world where engineering, science, education are all an exploratory videogame.".italic
100% * Center + pt 5 (@orbitmines.avatars.map => Link .location{: URL} => .platform.profile_picture).map(& px 2)
100% * Center Image{height == 120px} ./archive/on-orbits-equivalence-and-inconsistencies/images/header.png
100% * Center
X{width <= 500px}
X{width <= 500px}
100% * Author(@fadi, margin.top: 5%)
19 changes: 19 additions & 0 deletions orbitmines.com.ray/organizations.ray
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

@orbitmines = Organization(
avatars:
// These two would have to be registered under the orbitmines organization
| @"fadi.shawki@orbitmines.com" = Avatar()
| @"discord.orbitmines.com" = Avatar(@: "https://discord.orbitmines.com")

| @github.@orbitmines = Avatar(@: "https://github.com/orbitmines")
| @x.@OrbitMines = Avatar(@: "https://x.com/OrbitMines")
| @instagram.@orbitmines = Avatar(@: "https://www.instagram.com/orbitmines/")
| @youtube.@OrbitMines = Avatar(@: "https://www.youtube.com/@OrbitMines")
| @twitch.@orbitmines = Avatar(@: "https://www.twitch.tv/orbitmines")
| @linkedin.@orbitmines = Avatar(@: "https://www.linkedin.com/company/orbitmines/")
//| @gitlab.@orbitmines = Avatar(@: "https://gitlab.com/orbitmines") //TODO This would be set to private since it's not in use
| @facebook.@OrbitMines = Avatar(@: "https://www.facebook.com/profile.php?id=61550528503885")
profile_picture: ./lib/organizations/orbitmines/icon/orbitmines.icon.650x650.png
)

// TODO All the organizations
1 change: 1 addition & 0 deletions orbitmines.com.ray/profiles/fadi-shawki.ray
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Profile("fadi-shawki")
38 changes: 38 additions & 0 deletions orbitmines.com/app/Providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import React from 'react';
import { HotkeysProvider } from '@blueprintjs/core';
import { EtherOverlay } from '../src/@ether/UI';
import IEventListener from '../src/@orbitmines/js/react/IEventListener';
import Modules from '../src/@orbitmines/js/react/Modules';

const Metadata: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
<>
<meta property="og:site_name" content="OrbitMines" />
{/* https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards */}
<meta property="twitter:site" content="@OrbitMines" />
{children}
</>
);

const Providers: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const listener: IEventListener<any> = {
onKeyDown: (event): void => {
console.log(event.key);
},
};

return (
<Metadata>
{/* HotkeysProvider: https://blueprintjs.com/docs/#core/context/hotkeys-provider */}
<HotkeysProvider>
<Modules className="bp5-dark" listeners={[listener]}>
{children}
<EtherOverlay />
</Modules>
</HotkeysProvider>
</Metadata>
);
};

export default Providers;
7 changes: 7 additions & 0 deletions orbitmines.com/app/[...path]/CatchAllClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import EtherOrMinimap from '../../src/@ether/UI/router/EtherOrMinimap';

export default function CatchAllClient() {
return <EtherOrMinimap />;
}
14 changes: 14 additions & 0 deletions orbitmines.com/app/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CatchAllClient from './CatchAllClient';
import { PROFILE_NAMES } from '../profiles/[profile]/page';
export function generateStaticParams() {
return [
{ path: ['_catchall'] },
...Object.keys(PROFILE_NAMES).map((handle) => ({ path: [`@${handle}`] })),
];
}

export const dynamicParams = false;

export default function Page() {
return <CatchAllClient />;
}
7 changes: 7 additions & 0 deletions orbitmines.com/app/almanac/[[...section]]/AlmanacClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import Almanac from '../../../src/routes/Almanac';

export default function AlmanacClient() {
return <Almanac />;
}
53 changes: 53 additions & 0 deletions orbitmines.com/app/almanac/[[...section]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type {Metadata} from 'next';
import fs from 'fs';
import path from 'path';
import {sectionSlug} from '../../../src/lib/post/sectionSlug';
import AlmanacClient from './AlmanacClient';

const BOOK_TITLE = "Ether's Almanac";

// Sections live in the path (/almanac/<section-slug>) as client-side shallow
// routes within the book. Every section is prerendered as its own URL so that
// dev (next dev) and the production static export both serve them — and direct
// loads / refreshes don't 404.
//
// The section list is derived from the Almanac source at build time (string
// `head="..."` props are exactly the navigable sections, matching BookUtil), so
// it stays in sync automatically rather than via a hand-kept manifest. We keep
// the heading text too, so generateMetadata can title each section URL as
// "Ether's Almanac - <Section>" for search engines.
export function almanacSections(): {slug: string; head: string}[] {
const src = fs.readFileSync(
path.join(process.cwd(), 'src/routes/Almanac.tsx'),
'utf8',
);
const heads = [...src.matchAll(/<(?:Arc|Section)\s+head="([^"]+)"/g)].map((m) => m[1]);
const bySlug = new Map<string, string>();
for (const head of heads) {
const slug = sectionSlug(head);
if (slug && !bySlug.has(slug)) bySlug.set(slug, head);
}
return [...bySlug].map(([slug, head]) => ({slug, head}));
}

export function generateStaticParams() {
return [
{section: [] as string[]},
...almanacSections().map(({slug}) => ({section: [slug]})),
];
}

export const dynamicParams = false;

export async function generateMetadata(
{params}: {params: Promise<{section?: string[]}>},
): Promise<Metadata> {
const slug = (await params).section?.[0];
if (!slug) return {title: BOOK_TITLE};
const head = almanacSections().find((s) => s.slug === slug)?.head;
return {title: head ? `${BOOK_TITLE} - ${head.trim()}` : BOOK_TITLE};
}

export default function Page() {
return <AlmanacClient />;
}
7 changes: 7 additions & 0 deletions orbitmines.com/app/archive/[item]/ArchiveClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import Archive from '../../../src/routes/Archive';

export default function ArchiveClient() {
return <Archive />;
}
43 changes: 43 additions & 0 deletions orbitmines.com/app/archive/[item]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Metadata } from 'next';
import fs from 'fs';
import path from 'path';
import ArchiveClient from './ArchiveClient';

// Each archive slug maps to its source component (mirrors ITEMS in
// src/routes/Archive.tsx). The page title is read from that file's first
// `title: "..."` literal at build time, so titles stay in sync with the source
// rather than being hand-duplicated here.
export const ITEM_SOURCES: Record<string, string> = {
'2024-02-orbitmines-as-a-game-project': 'src/routes/archive/2024.02.OrbitMines_as_a_Game_Project.tsx',
'on-intelligibility': 'src/routes/archive/2022.OnIntelligibility.tsx',
'on-orbits-equivalence-and-inconsistencies': 'src/routes/archive/2023.OnOrbits.tsx',
'towards-a-universal-language': 'src/routes/archive/2025.TowardsAUniversalLanguage.tsx',
'the-orbitmines-minecraft-server': 'src/routes/archive/2026.MinecraftArchive.tsx',
};

// Reads the reference object's `title` literal so the static <title> is owned
// by Next metadata. The description is rendered by the paper itself (Post),
// so it isn't duplicated here.
function itemTitle(item: string): string | undefined {
const source = ITEM_SOURCES[item];
if (!source) return undefined;
const src = fs.readFileSync(path.join(process.cwd(), source), 'utf8');
return src.match(/title:\s*"((?:[^"\\]|\\.)*)"/)?.[1];
}

export function generateStaticParams() {
return Object.keys(ITEM_SOURCES).map((item) => ({ item }));
}

export const dynamicParams = false;

export async function generateMetadata(
{ params }: { params: Promise<{ item: string }> },
): Promise<Metadata> {
const title = itemTitle((await params).item);
return title ? { title } : {};
}

export default function Page() {
return <ArchiveClient />;
}
36 changes: 36 additions & 0 deletions orbitmines.com/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Metadata, Viewport } from 'next';
import '../src/lib/blueprintjs/css/blueprint.css';
import '../src/lib/blueprintjs/css/blueprint-icons.css';
import '../src/lib/index.scss';
import Providers from './Providers';

export const metadata: Metadata = {
// Per-page <meta name="description"> is rendered by each page's content
// (Post/Minimap/profile) so it can be section/paper-specific; no global
// default here, which would otherwise duplicate or override those.
title: 'OrbitMines Research',
manifest: '/manifest.json',
icons: {
icon: [{ url: '/favicon.png', type: 'image/png', sizes: '64x64' }],
apple: '/logo192.png',
},
};

export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#000000',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<Providers>{children}</Providers>
</div>
</body>
</html>
);
}
10 changes: 10 additions & 0 deletions orbitmines.com/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import EtherOrMinimap from '../src/@ether/UI/router/EtherOrMinimap';

// Cloudflare Pages routes unknown URLs to /index.html with 200 via the
// _redirects rule, so this 404.html is rarely hit. We still wire it up to
// the same SPA-routing component as a defensive fallback.
export default function NotFound() {
return <EtherOrMinimap />;
}
10 changes: 10 additions & 0 deletions orbitmines.com/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client';

import EtherOrMinimap from '../src/@ether/UI/router/EtherOrMinimap';

// Root + SPA-fallback target (Cloudflare _redirects sends unknown URLs here
// with HTTP 200). EtherOrMinimap inspects the path and renders EtherRoutes for
// /@user, /$lang and Minimap otherwise.
export default function Page() {
return <EtherOrMinimap />;
}
16 changes: 16 additions & 0 deletions orbitmines.com/app/papers/[[...slug]]/PapersRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

// Legacy /papers/* URL space — redirect into /archive/* keeping the suffix.
export default function PapersRedirect() {
const router = useRouter();

useEffect(() => {
const suffix = window.location.pathname.replace(/^\/papers\/?/, '');
router.replace(`/archive/${suffix}`);
}, [router]);

return null;
}
16 changes: 16 additions & 0 deletions orbitmines.com/app/papers/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Metadata } from 'next';
import PapersRedirect from './PapersRedirect';

export const metadata: Metadata = { title: 'Papers' };

export function generateStaticParams() {
// Generate the bare /papers entry; deep `/papers/...` paths fall back to
// the 404 page which also renders this redirect logic from the URL.
return [{ slug: [] as string[] }];
}

export const dynamicParams = false;

export default function Page() {
return <PapersRedirect />;
}
18 changes: 18 additions & 0 deletions orbitmines.com/app/profiles/[profile]/ProfileRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';

import { useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';

// /profiles/:profile redirects to /@:profile. Server-side redirect would be
// nicer, but `output: 'export'` has no server, so we do it on the client
// immediately on mount.
export default function ProfileRedirect() {
const params = useParams<{ profile: string }>();
const router = useRouter();

useEffect(() => {
if (params?.profile) router.replace(`/@${params.profile}`);
}, [params, router]);

return null;
}
Loading
Loading