diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 2cb7d2a..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/*.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 8ed3589..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "es2020": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 11, - "sourceType": "module" - }, - "plugins": ["unused-imports", "@typescript-eslint", "prettier"], - "rules": { - "prettier/prettier": "error", - "no-unused-vars": "off", - "unused-imports/no-unused-imports-ts": "error", - "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/ban-ts-comment": "off" - } -} diff --git a/.gitignore b/.gitignore index 317395e..d271d11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist +.astro target .DS_Store diff --git a/.prettierrc b/.prettierrc index afe8ac1..70d7dc0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,13 @@ "tabWidth": 2, "singleQuote": true, "endOfLine": "lf", - "importOrder": ["^[./]", ".*", ".scss$"], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true + "plugins": ["prettier-plugin-astro"], + "overrides": [ + { + "files": "*.astro", + "options": { + "parser": "astro" + } + } + ] } diff --git a/BLOG_REWRITE_PLAN.md b/BLOG_REWRITE_PLAN.md new file mode 100644 index 0000000..c5cfe60 --- /dev/null +++ b/BLOG_REWRITE_PLAN.md @@ -0,0 +1,1099 @@ +# Personal Blog Rewrite Plan + +## Goal + +Replace the existing portfolio with a statically generated personal blog that still helps with hiring, but earns that trust through writing, project essays, and technical evidence instead of a conventional portfolio timeline. + +The site should feel current, minimal, and carefully made. It should not imitate old plain-HTML blogs, but it should learn from why many respected technical blogs work: durable URLs, direct navigation, readable typography, fast pages, RSS, and content that carries the authority. + +This plan incorporates the rendered review of 100 CS/technical blogs and a 10-lens review pass covering content, visual design, mobile ergonomics, accessibility, performance, information architecture, implementation risk, hiring signal, typography, and content migration. + +## Inputs + +### Existing Repo + +The current repo is a custom TypeScript/Webpack single-page portfolio. The strongest reusable material is in: + +- `src/data/projects/*.ts`: project titles, date strings, summaries, longer paragraphs, media references, external links +- `src/data/media`: screenshots, posters, videos, CV, BSc/MSc thesis PDFs +- `static/no-change`: favicons, `robots.txt`, 404 page, Open Graph image + +The current implementation should be replaced rather than evolved. It depends on JavaScript for core content and is organized around timeline cards, expanded details, image viewers, theme controls, and decorative UI. The new site should ship little to no JavaScript and render normal pages as static HTML. + +### Rendered Blog Research + +I installed Playwright and rendered 100 technical/personal CS blogs at desktop and phone widths. The artifacts are in: + +- `/tmp/codex-blog-design-pass/index.html` +- `/tmp/codex-blog-design-pass/summary.json` +- `/tmp/codex-blog-design-pass/summary.csv` + +Useful metrics from the pass: + +- Median desktop paragraph width: about `662px` +- Median mobile paragraph width: about `350px` +- Median paragraph font size: `16px` +- 73 of 100 exposed RSS or Atom +- Most strong personal blogs used very few card-like UI elements +- Several famous blogs had mobile overflow, which this site should explicitly avoid +- The best visual references used media as explanation, not decoration + +Representative references: + +- Writing-first archive: [Simon Willison](https://simonwillison.net/), [Marc Brooker](https://brooker.co.za/blog/), [Brandur](https://brandur.org/articles), [Armin Ronacher](https://lucumr.pocoo.org/), [Phil Eaton](https://notes.eatonphil.com/) +- Minimal but current personal sites: [Tom MacWright](https://macwright.com/), [Thorsten Ball](https://thorstenball.com/), [matklad](https://matklad.github.io/), [BurntSushi](https://burntsushi.net/) +- Technical visual writing: [Red Blob Games](https://www.redblobgames.com/), [Distill](https://distill.pub/), [Bret Victor](https://worrydream.com/), [Jay Alammar](https://jalammar.github.io/), [fasterthanli.me](https://fasterthanli.me/) +- Strong code-heavy writing: [Ned Batchelder](https://nedbatchelder.com/blog/), [Dave Cheney](https://dave.cheney.net/), [Eli Bendersky](https://eli.thegreenplace.net/), [Laurence Tratt](https://tratt.net/laurie/blog/) + +## Product Direction + +The site should be a blog first and a hiring surface second. + +The homepage should not say "hire me" first. It should establish a serious technical writer and builder through the quality of the archive. Recruiters should still quickly find the About page, CV, GitHub, LinkedIn, contact details, and the most relevant project essays. + +The tone should be: + +- Text-first +- Calm +- Precise +- Modern +- Functional +- Slightly editorial +- Never flashy +- Never intentionally dated + +Avoid: + +- Purple or blue-purple gradients +- Glowing blobs +- Timeline cards +- Hero illustrations +- Glassmorphism +- Decorative animations +- Dense nav chrome +- Flat boxes pretending to be a "retro" blog +- Landing-page copy +- Skill grids, logo walls, metric blocks, or resume-card layouts + +## Key Decisions + +- Canonical essays live under `/writing/[slug]/`, including project essays. +- `/projects/` is a complete text index of built work with stable anchors; it is not a second set of duplicate project detail pages. +- Tags are Phase 1 metadata but tag pages can wait. +- The visual system uses a refined sans-serif voice, not Times New Roman and not browser defaults. +- Normal pages ship no client JavaScript. Any exception must be post-specific and justified. +- Images that need optimization live in `src/assets` and render through Astro image tooling. `public/` is for stable raw files such as PDFs, favicons, legacy assets, and videos/downloads. +- The deployment plan must match the existing Forgejo workflow that builds `dist/` and rsyncs it to `/pages/schmelczer-dev`. + +## Design Principles + +### Function Includes Reading + +Minimal design is not an excuse for weak typography. The site should feel better on a phone than most personal blogs. + +Core reading requirements: + +- No horizontal scrolling at `320px`, `390px`, or `430px` +- Prose width around `36rem-42rem` on desktop +- Mobile content width: `min(100% - 2 * var(--gutter), var(--measure))` +- Mobile gutter: `clamp(16px, 5vw, 24px)` +- Body font size: `17px` on phones, `17px-18px` on larger screens +- Prose line height: `1.62-1.68` +- Paragraph spacing tuned for long reading, not marketing skimming +- Code blocks scroll horizontally only inside the code block +- Figures, tables, long links, and code never force page overflow +- Header nav, footer links, RSS/contact links, project links, footnotes, backlinks, and pagination use a `44px` minimum hit area or equivalent padding + +### Typography + +Use a refined sans-serif as the main voice. Do not use Times New Roman. Do not make the site look like default browser HTML. + +Recommended direction: + +- Body and UI: `Source Sans 3` first, then `Inter`, then the system sans stack +- Code: `IBM Plex Mono`, `JetBrains Mono`, or a careful system monospace stack +- No custom display font +- No negative letter-spacing +- Use font weights sparingly: regular, medium, semibold + +Default recommendation: + +```css +font-family: + 'Source Sans 3', + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; +``` + +Font loading policy: + +- Start with the system stack or one self-hosted variable WOFF2 for `Source Sans 3` +- Use only regular-to-semibold weights +- Use `font-display: swap` +- Preload only the single above-fold face if measurement proves it helps +- Avoid third-party font requests + +Editorial type targets: + +- Body: `17px`, line-height `1.65` +- Article `h1`: about `2rem` on phones, restrained on desktop, line-height `1.15-1.2` +- `h2`: line-height `1.2-1.25`, with generous space above +- Metadata and captions: `14px-15px`, line-height `1.35-1.45` +- Code: about `.88em`, line-height `1.55` + +### Color + +Use a quiet near-white background and near-black text. Let links and small metadata carry the color. + +Candidate palette: + +- Background: `#fbfaf7` +- Text: `#181817` +- Muted text: `#6b6860` +- Rule/border: `#d9d5ca` +- Strong rule/focus: `#7f7668` +- Link: `#285f74` +- Link hover: `#8a4b2f` +- Code background: `#efede6` +- Callout background: `#f4f1e8` + +The palette should feel warm and literary without becoming beige-heavy. The teal link is the only real accent. Purple is out. + +Low-contrast tokens such as `--rule`, `--code-bg`, and `--callout-bg` must not be the only focus, active, or error indicator. + +### Link Treatment + +Prose links should look like links. + +```css +.prose a { + color: var(--link); + text-decoration-line: underline; + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; +} +``` + +Hover can shift color. Keyboard focus must use a visible `2px+` outline with offset. Do not remove outlines. + +### Layout + +Use a simple centered layout with a narrow content column and a slightly wider media column. + +Desktop: + +- Page max width around `72rem` +- Main article column around `40rem` +- Figures can break out to `56rem-64rem` when useful +- Header aligned with content, not full-width heavy navigation +- Footer minimal + +Mobile: + +- Single column +- Header wraps naturally; no menu JavaScript +- Nav uses `flex-wrap: wrap`, small gaps, and no pill labels that can force overflow +- Site title and nav may become two short rows +- Article title should not dominate the whole first viewport +- First paragraph should appear without excessive scrolling +- Metadata should be visible but quiet + +### UI Shape + +Use rules, whitespace, indentation, and typographic hierarchy instead of cards. + +Default primitives: + +- Thin horizontal rules +- Muted metadata +- Hanging dates on desktop where appropriate +- Indented summaries +- Captions +- Narrow dividers +- Text rows for archive/project entries + +Acceptable framed elements: + +- Code blocks +- Tables +- Figures with captions +- Callouts for short notes +- A small "At a glance" metadata block inside project essays when it is content, not decoration + +Avoid using cards for every post preview. Archive entries should mostly be text rows with date, title, tags, and one sentence. + +### Figures, Tables, and Code + +Code blocks: + +- `overflow-x: auto` +- `max-width: 100%` +- `-webkit-overflow-scrolling: touch` +- No wrapping by default +- Mobile code font around `13px-14px` +- `padding: 1rem` +- `border: 1px solid var(--rule)` +- `border-radius: 6px` +- No box shadow + +Tables: + +- Small tables stay full width +- Wide tables live inside an overflow container +- Use tabular numerals where useful +- Use subtle row rules +- Avoid zebra striping unless the data genuinely needs it + +Figures: + +- `margin-block: 2rem` +- Captions in muted text at `14px-15px` +- `img`, `video`, `canvas`, and `svg` use `max-width: 100%; height: auto` +- Optional `border: 1px solid var(--rule)` for screenshots +- Wide figures only above tablet widths +- Avoid `width: 100vw`; use `max-inline-size: min(100%, var(--measure-wide))` + +### Media + +Media should be evidence. + +Good uses: + +- A screenshot showing the UI state being discussed +- A short video demonstrating an engine, simulation, or interaction +- A diagram that explains architecture or data flow +- A small inline image that makes a project concrete + +Bad uses: + +- Decorative thumbnails for every post +- Hero images +- Cropped atmospheric images +- Large videos above the fold +- Autoplay + +For project posts, use one strong media item near the top only when it immediately clarifies the subject. Prefer "evidence figure" or "lead figure" language, not "hero." + +Videos: + +- Never load videos in archive/listing pages +- Use posters +- Set explicit dimensions +- Use `preload="metadata"` or `preload="none"` +- Provide captions, transcript, or a text summary when the video carries meaning +- Compress large MP4/WebM files before publishing +- Set a per-page media budget before launch + +## Information Architecture + +### Routes + +```text +/ +/writing/ +/writing/[slug]/ +/projects/ +/about/ +/rss.xml +/sitemap-index.xml +``` + +Optional later: + +```text +/notes/ +/tags/[tag]/ +/now/ +``` + +Project essays are canonical writing posts. The project index links to those posts and to demos/source/packages/papers. Do not create duplicate `/projects/[slug]/` pages in the first version; use stable anchors such as `/projects/#ad-astra` for smaller items if needed. + +### Navigation + +Persistent header: + +- `Writing` +- `Projects` +- `About` +- `RSS` + +Footer: + +- Email +- CV +- GitHub +- LinkedIn +- RSS + +Keep the header compact and text-only. Put dense hiring links in the About page and footer rather than in the primary nav. + +### Homepage + +Purpose: establish an editorial surface first, then make the best built work easy to find. + +Structure: + +1. Compact intro +2. Latest writing +3. Selected project essays +4. Selected technical work +5. Footer with RSS/contact links + +Example intro direction: + +> I'm Andras Schmelczer. I write about building software systems, AI deployment, graphics, simulations, and tools. + +No large hero. No headshot on the homepage unless it is tiny and secondary. Avoid badges, thumbnails, big feature blocks, and ranking language. "Selected" should be a quiet editorial choice, not a portfolio showcase. + +### Writing Index + +Chronological archive with tasteful grouping. + +Rules: + +- Group by year +- Newest first +- Show all posts +- Tags are inline metadata +- Tag pages can wait, but tags must exist in frontmatter from Phase 1 +- Use "selected" markers very sparingly, if at all + +Each entry: + +- Date +- Title +- One-sentence summary +- Tags + +Avoid card grids. A reader should be able to scan the archive quickly on a phone. + +### Project Index + +This is not the old portfolio timeline. It is an index of things built. + +Groups: + +- Selected projects +- Older and smaller projects + +Each row: + +- Name +- Problem, constraint, or essay angle +- Year or period +- Quiet technology metadata +- Primary link to essay when one exists +- Secondary links: demo, source, package, paper, video + +Use technologies as evidence, not as the lead. Every legacy project should be accounted for either as a row or as part of a merged row, even when it does not deserve a full essay. + +### About + +This is the hiring-focused page. + +Sections: + +- Short bio +- Quick facts: target role/domain, location/remote preference, availability if relevant, email, CV, GitHub, LinkedIn +- Best starting points: 3-5 strongest essays +- What I work on +- Selected technical work +- Technical strengths +- Experience/education summary + +This page can be more direct: + +- MSc in Computer Science +- Professional software engineering experience +- AI/ML systems, large-scale architecture, graphics, visualization, embedded projects +- Preference for complex, multidisciplinary systems + +Keep it typographic and compact. No skill grids, logo walls, metric blocks, or resume cards. + +## Content Plan + +### Editorial Standard + +The first version should not read like project cards converted to Markdown. The project material should become technical essays, some of which happen to come from past projects. + +Every substantial post should answer: + +- Does this teach something to a technical reader who is not evaluating me? +- Does the title make a specific promise? +- Is there a thesis, reader takeaway, and evidence? +- Is there at least one concrete constraint, number, diagram, code excerpt, or failure? +- Is the project age clearly framed? +- Are technologies evidence rather than the point? +- Would the post still be worth reading if my name and CV links disappeared? + +### Initial Essays + +Use essay angles rather than project labels: + +1. `Designing an ML deployment API around best practices` from GreatAI +2. `Tile-based optimization for 2D SDF ray tracing` from SDF-2D +3. `Shared simulation code in a mobile multiplayer browser game` from decla.red +4. `A 50 FPS game engine on an ATtiny85` from ad_astra +5. `Syncing state with immutable tries` from life-towers +6. `Graph models for a real-time cooling simulation` from the nuclear simulator and graph editor + +Best recruiter starting set: + +- GreatAI +- SDF-2D +- life-towers or nuclear-simulation + +Secondary project rows or shorter notes: + +- City simulation in Unity +- Forex prediction experiment +- My Notes Android app +- Platform game in C/SDL +- LED music visualizer +- Photo site generator +- Avoid +- Photo colour grader + +Add 2-3 non-project technical notes shortly after launch so the site does not feel like a portfolio in blog clothing. Good candidates: + +- Lessons from deploying ML systems +- Notes on mobile graphics performance +- What makes old side projects still worth writing about +- A debugging or architecture retrospective from recent work + +### Date Framing + +Separate project dates from publication dates. + +- `date`: publication date for RSS and archive ordering +- `updated`: material revision date +- `projectPeriod`: human text such as `Autumn-Winter 2020` +- `sortDate`: approximate ISO date for project ordering + +Old projects should be framed as current reflection on past work, not as current products. + +### Post Template + +Each project essay should follow a consistent loose structure: + +```markdown +--- +title: +description: +date: +updated: +projectPeriod: +tags: +project: +role: +stack: +scale: +outcome: +links: + - label: + type: + url: +media: + - type: + src: + poster: + mp4: + webm: + alt: + caption: + transcript: + role: evidence +--- + +Intro: the thesis, why this is worth reading, and what the reader will learn. + +## At a glance + +Role, timeframe, stack, scale, outcome, links. + +## The Problem + +## Constraints + +## Design + +## What Worked + +## What I Would Change + +## Links +``` + +This should not become rigid. Some posts need a story; others need an architecture walkthrough. + +### Writing Style + +Prefer: + +- Specific constraints +- Numbers +- Concrete tradeoffs +- Architecture sketches +- What shipped +- What improved +- Who used it, if known +- What was measured +- What I personally owned +- What failed +- What changed your mind +- Short code excerpts when useful + +Avoid: + +- "I was passionate about..." +- Generic technology lists +- Resume phrasing +- Overexplaining old projects as if they are current products +- Hiding rough edges + +Rough edges are useful if framed as engineering judgment. + +## Migration Plan + +### Migration Manifest + +Create a migration manifest before rewriting content. It should map executable TS project records into static content. + +Fields: + +```ts +{ + sourceProjectId: string; + sourceFile: string; + slug: string; + target: "essay" | "project-row" | "merged-project-row"; + canonicalPath: string; + legacyAnchor?: string; + title: string; + essayTitle?: string; + publishedDate?: string; + projectPeriod: string; + sortDate: string; + bodySources: Array<"description" | "more">; + links: Array<{ label: string; type: "source" | "demo" | "package" | "paper" | "video" | "site"; url: string; download?: boolean }>; + media: Array<{ type: "image" | "video" | "preview"; src?: string; poster?: string; mp4?: string; webm?: string; externalUrl?: string; alt: string; caption: string }>; + downloads: Array<{ label: string; url: string }>; +} +``` + +Initial mapping: + +| Source | Target | Notes | +| ---------------------------------- | --------------------------------- | ---------------------------------------------------------- | +| `great-ai.ts` | Essay | Use `great-ai.png`, MSc thesis PDF, PyPI/site links | +| `sdf2d.ts` | Essay | Use `sdf2d.png`, NPM, YouTube, demo; cross-link BSc thesis | +| `declared.ts` | Essay | Use `decla-red.png`, GitHub, demo, BSc thesis | +| `ad-astra.ts` | Essay | Use poster and MP4/WebM; GitHub link | +| `towers.ts` | Essay or selected project row | Use `towers.png`, GitHub, demo; focus on trie sync | +| `nuclear.ts` + `nuclear-editor.ts` | One essay or selected project row | Merge simulator/editor into one story with two screenshots | +| `city-simulation.ts` | Project row or short note | Use video only inside detail writing if kept | +| `forex.ts` | Project row or short note | Frame carefully as experiment, not financial claim | +| `my-notes.ts` | Older project row | Android/Markdown note app | +| `platform-game.ts` | Older project row | Early C/SDL learning story | +| `leds.ts` | Older project row | Raspberry Pi, FFT, LEDs | +| `photos.ts` | Older project row | Static photo site generator | +| `avoid.ts` | Older project row | First web game, external demo | +| `colors.ts` | Older project row | Color-grader proof of concept | + +Migration copyedit rules: + +- Decode HTML entities such as `—` and `&` +- Replace generic icon/helper labels with semantic labels +- Preserve external URLs +- Replace generic video alt text with project-specific alt/captions +- Keep BSc/MSc PDFs with explicit labels +- Cross-link shared thesis context between SDF-2D and decla.red + +### Link Labels + +Normalize links to reader-facing labels: + +- `Source` +- `Demo` +- `Package` +- `Paper` +- `Thesis` +- `Video` +- `Project site` + +Do not use helper-derived labels such as "Open in new tab" in content. + +## Technical Plan + +### Framework + +Use Astro. + +Reasons: + +- Static generation by default +- Markdown content collections +- Little-to-no client JavaScript +- Good TypeScript fit for this repo +- Straightforward RSS/sitemap support +- Easy to preserve a content-first architecture + +Do not use Typst as the primary web writing format for now. Typst's HTML export is still not the right foundation for this production blog. Use Markdown for posts. Typst can be revisited later for PDFs or long-form downloadable documents. + +### JavaScript Policy + +Default: ship no client JavaScript. + +Allowed later only if it materially improves a specific post: + +- A small interactive diagram +- A progressive enhancement for a specific demo +- A tiny script for theme preference if dark mode is added + +Hard rules: + +- No global app shell +- No client-side routing +- No required JS for reading +- No `client:*` islands in normal pages +- No global view transitions +- No global prefetching +- No analytics script unless explicitly accepted as a measured exception + +QA gates: + +- Run Playwright with JavaScript disabled and verify reading, nav, RSS discovery, media access, archive scanning, and project links +- Fail CI if normal pages emit unexpected `_astro/*.js` or ` diff --git a/src/components/ProjectLinks.astro b/src/components/ProjectLinks.astro new file mode 100644 index 0000000..69131cf --- /dev/null +++ b/src/components/ProjectLinks.astro @@ -0,0 +1,28 @@ +--- +interface Link { + label: string; + type: string; + url: string; + download?: boolean; +} + +interface Props { + links: Link[]; +} + +const { links } = Astro.props; +--- + +{ + links.length > 0 && ( + + ) +} diff --git a/src/components/ProjectList.astro b/src/components/ProjectList.astro new file mode 100644 index 0000000..de5f6df --- /dev/null +++ b/src/components/ProjectList.astro @@ -0,0 +1,81 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import { Image } from 'astro:assets'; +import { articlePath } from '../lib/site'; +import ProjectLinks from './ProjectLinks.astro'; + +interface Props { + projects: CollectionEntry<'projects'>[]; +} + +const { projects } = Astro.props; +type ProjectLink = CollectionEntry<'projects'>['data']['links'][number]; +--- + +
    + { + projects.map((project) => { + const anchor = project.data.legacyAnchor ?? project.data.sourceProjectId; + const titleId = `${anchor}-title`; + const essayHref = project.data.essay ? articlePath(project.data.essay) : undefined; + const essayLink: ProjectLink | undefined = essayHref + ? { label: 'Article', type: 'site', url: essayHref } + : undefined; + const primaryHref = essayHref ?? project.data.links[0]?.url; + const links: ProjectLink[] = [ + ...(essayLink ? [essayLink] : []), + ...project.data.links, + ]; + + if (links.length === 0) { + links.push({ label: 'Permalink', type: 'site', url: `#${anchor}` }); + } + + return ( +
  1. + {primaryHref ? ( + + {project.data.thumbnail.alt} + + ) : ( +
    + {project.data.thumbnail.alt} +
    + )} +
    +

    + {primaryHref ? ( + {project.data.title} + ) : ( + project.data.title + )} +

    +

    {project.data.description}

    +

    + {project.data.period} · {project.data.technologies.join(', ')} +

    +
    +
    + +
    +
  2. + ); + }) + } +
diff --git a/src/components/TagList.astro b/src/components/TagList.astro new file mode 100644 index 0000000..97bd005 --- /dev/null +++ b/src/components/TagList.astro @@ -0,0 +1,22 @@ +--- +import { formatTag, tagPath } from '../lib/site'; + +interface Props { + tags: string[]; + currentTag?: string; +} + +const { tags, currentTag } = Astro.props; +--- + + diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..9b83040 --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,108 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; +import { z } from 'astro/zod'; + +const linkSchema = z.object({ + label: z.string(), + type: z.enum([ + 'source', + 'demo', + 'package', + 'paper', + 'thesis', + 'video', + 'site', + 'contact', + ]), + url: z.string(), + download: z.boolean().optional(), +}); + +const thumbnailSchema = ({ image }: { image: any }) => + z.object({ + src: image(), + alt: z.string(), + }); + +const mediaSchema = ({ image }: { image: any }) => + z + .object({ + type: z.enum(['image', 'video', 'diagram']), + src: image().optional(), + poster: image().optional(), + mp4: z.string().optional(), + webm: z.string().optional(), + alt: z.string().optional(), + decorative: z.boolean().optional(), + caption: z.string().optional(), + transcript: z.string().optional(), + role: z.enum(['evidence', 'og', 'inline']).default('evidence'), + }) + .refine((item) => item.decorative || Boolean(item.alt), { + message: 'Meaningful media needs alt text.', + path: ['alt'], + }) + .refine((item) => item.decorative || Boolean(item.caption), { + message: 'Meaningful media needs a caption.', + path: ['caption'], + }); + +const posts = defineCollection({ + loader: glob({ pattern: '**/*.md', base: './src/content/posts' }), + schema: ({ image }) => + z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + updated: z.coerce.date().optional(), + draft: z.boolean().default(false), + thumbnail: thumbnailSchema({ image }), + tags: z.array( + z.enum([ + 'ai', + 'systems', + 'graphics', + 'simulation', + 'embedded', + 'web', + 'tools', + 'games', + ]) + ), + selected: z.boolean().default(false), + featuredOrder: z.number().optional(), + project: z.string().optional(), + projectPeriod: z.string().optional(), + role: z.string().optional(), + stack: z.array(z.string()).optional(), + scale: z.string().optional(), + outcome: z.string().optional(), + audience: z + .enum(['general', 'technical', 'recruiter-relevant']) + .default('technical'), + links: z.array(linkSchema).default([]), + media: z.array(mediaSchema({ image })).default([]), + }), +}); + +const projects = defineCollection({ + loader: glob({ pattern: '**/*.md', base: './src/content/projects' }), + schema: ({ image }) => + z.object({ + sourceProjectId: z.string(), + title: z.string(), + description: z.string(), + thumbnail: thumbnailSchema({ image }), + period: z.string(), + sortDate: z.coerce.date(), + status: z.string().optional(), + technologies: z.array(z.string()).default([]), + selected: z.boolean().default(false), + essay: z.string().optional(), + legacyAnchor: z.string().optional(), + links: z.array(linkSchema).default([]), + downloads: z.array(z.object({ label: z.string(), url: z.string() })).default([]), + }), +}); + +export const collections = { posts, projects }; diff --git a/src/data/media/ad_astra.jpg b/src/content/posts/_assets/ad-astra.jpg similarity index 100% rename from src/data/media/ad_astra.jpg rename to src/content/posts/_assets/ad-astra.jpg diff --git a/src/data/media/decla-red.png b/src/content/posts/_assets/decla-red.png similarity index 100% rename from src/data/media/decla-red.png rename to src/content/posts/_assets/decla-red.png diff --git a/src/data/media/great-ai.png b/src/content/posts/_assets/great-ai.png similarity index 100% rename from src/data/media/great-ai.png rename to src/content/posts/_assets/great-ai.png diff --git a/src/data/media/process-simulator-input.jpg b/src/content/posts/_assets/process-simulator-input.jpg similarity index 100% rename from src/data/media/process-simulator-input.jpg rename to src/content/posts/_assets/process-simulator-input.jpg diff --git a/src/data/media/process-simulator.jpg b/src/content/posts/_assets/process-simulator.jpg similarity index 100% rename from src/data/media/process-simulator.jpg rename to src/content/posts/_assets/process-simulator.jpg diff --git a/src/data/media/sdf2d.png b/src/content/posts/_assets/sdf2d.png similarity index 100% rename from src/data/media/sdf2d.png rename to src/content/posts/_assets/sdf2d.png diff --git a/src/data/media/towers.png b/src/content/posts/_assets/towers.png similarity index 100% rename from src/data/media/towers.png rename to src/content/posts/_assets/towers.png diff --git a/src/content/posts/ad-astra-attiny85-game-engine.md b/src/content/posts/ad-astra-attiny85-game-engine.md new file mode 100644 index 0000000..2768f08 --- /dev/null +++ b/src/content/posts/ad-astra-attiny85-game-engine.md @@ -0,0 +1,65 @@ +--- +title: A 50 FPS Game Engine on an ATtiny85 +description: Building a tiny embedded game engine around an ATtiny85V, OLED display, IR input, EEPROM persistence, and a custom PCB. +date: 2026-05-06 +projectPeriod: 'Spring 2020' +thumbnail: + src: ./_assets/ad-astra.jpg + alt: The Ad Astra game running on a small OLED display. +tags: ['embedded', 'games', 'systems'] +selected: true +featuredOrder: 5 +project: ad-astra +role: Hardware and firmware author +stack: ['C', 'ATtiny85V', 'OLED', 'EEPROM', 'PCB design'] +scale: 8-bit microcontroller, 8 MHz clock, 15-20 ms maximum frame times during gameplay +outcome: A working low-power handheld game engine and game built from the circuit board up +audience: technical +links: + - label: Source + type: source + url: https://github.com/schmelczer/ad_astra +media: + - type: video + poster: ./_assets/ad-astra.jpg + webm: /media/video/ad_astra.webm + mp4: /media/video/ad_astra.mp4 + alt: Video demonstration of the embedded game running on a small OLED display. + caption: The game engine ran on an ATtiny85V with an OLED display and IR input. +--- + +Ad Astra came from wanting to combine graphics and microcontrollers without hiding behind a large development board. The result was a small embedded game engine and game built around an ATtiny85V, an OLED display, IR input, EEPROM persistence, and a custom PCB. + +The fun part was that every layer mattered. The circuit, display driver, memory layout, object model, sprite tooling, and game loop all had to fit inside a tiny system. + +## The Problem + +The hardware setup was intentionally constrained: an ATtiny85V, a D096-12864-SPI7 OLED display, a TSOP4838 IR receiver, and a 3.3V regulator. The system was low power, with peak consumption around 31 mW at full brightness and a standby mode around 1.5 mA. + +Those numbers made the project feel physical. Performance was not an abstract target. Every frame and every byte had a cost. + +## Constraints + +The engine ran at 8 MHz on an 8-bit ALU. That meant the display driver and game loop had to avoid expensive generality. + +Even the programming model needed restraint. I wrote the firmware in C, but used a balance of structured and object-oriented ideas to keep game object behavior manageable without paying for a runtime that did not exist. + +## Design + +The display driver was the most performance-sensitive layer. I used SIMD-like techniques on the 8-bit ALU to process four pixels at once. That helped keep maximum frame times between 15 and 20 milliseconds during gameplay, so the lowest gameplay frame rate stayed above 50 FPS. + +For game objects, I used prototype-based inheritance. It was a pragmatic way to reuse behavior while keeping the implementation simple enough for the target. + +Persistent state used the built-in EEPROM with an atomic commit approach. Sprite data also lived in EEPROM, and I wrote scripts to convert PNG sprites into C array definitions so assets could move into firmware cleanly. + +## What Worked + +The project worked because the abstraction level stayed close to the hardware. The engine had reusable pieces, but none of them pretended the platform was larger than it was. + +The custom PCB also changed the project. Once the system had a real board, bugs felt less like software inconveniences and more like design consequences. That made the final result much more satisfying. + +## What I Would Change + +Today I would write a more explicit development log around the display driver and persistence layer. Those are the parts that still feel technically interesting, and they deserve diagrams and measurements. + +I would also add a small emulator or host-side harness. Debugging firmware directly on constrained hardware is useful, but a fast feedback loop would have made the engine easier to evolve. diff --git a/src/content/posts/declared-shared-simulation-code.md b/src/content/posts/declared-shared-simulation-code.md new file mode 100644 index 0000000..5d6d629 --- /dev/null +++ b/src/content/posts/declared-shared-simulation-code.md @@ -0,0 +1,70 @@ +--- +title: Shared Simulation Code in a Mobile Multiplayer Browser Game +description: How decla.red used shared TypeScript game logic, WebSockets, client prediction, and spatial indexing for a team-based browser game. +date: 2026-05-07 +projectPeriod: 'Autumn-Winter 2020' +thumbnail: + src: ./_assets/decla-red.png + alt: The decla.red browser game interface showing a space scene. +tags: ['games', 'web', 'systems'] +selected: true +featuredOrder: 4 +project: declared +role: Game and backend systems author +stack: ['TypeScript', 'Node.js', 'WebSockets', 'Firebase', 'WebGL'] +scale: Multiple servers, each communicating with 16-32 clients +outcome: A mobile-capable online browser game built on top of SDF-2D +audience: technical +links: + - label: Source + type: source + url: https://github.com/schmelczer/decla.red + - label: Demo + type: demo + url: https://decla.red + - label: BSc thesis + type: thesis + url: /media/downloads/sdf2d-andras-schmelczer.pdf + download: true +media: + - type: image + src: ./_assets/decla-red.png + alt: The decla.red browser game interface showing a space scene with team controls and planets. + caption: decla.red used the SDF-2D renderer in a real-time multiplayer game. +--- + +`decla.red` was a conquest-style online multiplayer browser game set in space. Two teams fought over small planets, gained points based on control, and could shoot at the other team while moving through a ray-traced 2D scene. + +The rendering made the game look interesting, but the architecture was the more useful lesson. The game needed to run on phones, talk to multiple servers, keep clients responsive, and avoid duplicating game rules between frontend and backend. + +## The Problem + +Real-time multiplayer games have an awkward split. The server should be authoritative, but the client has to feel immediate. If every meaningful interaction waits for a round trip, the game feels broken. If the client is trusted too much, the game becomes inconsistent or easy to abuse. + +For this project, I wanted the same game rules to be used by the server and the client. The server would calculate the actual next state. The client could predict locally with the same code and later reconcile with the server. + +## Constraints + +The project used TypeScript on both sides: browser code for the client and Node.js for the server. WebSockets carried real-time updates. Firebase helped the servers reach consensus about the active server set. + +Each server communicated with 16-32 clients. That is not large by industry standards, but it was enough to make careless spatial operations and state updates visible. + +## Design + +The key decision was a shared library for game logic. Both the client and server linked to it, so the transition rules lived in one place. + +That reduced a common source of bugs: the client and server disagreeing about the meaning of an action. It also made client-side prediction more realistic, because the client was not approximating a different system. + +As the game logic became heavier, spatial operations needed attention. I implemented k-d trees to reduce the cost of queries over objects in the world. For the object model, I borrowed ideas from message passing, including a version of the Smalltalk-style `messageNotUnderstood` pattern, to keep behavior extensible without pushing every entity into a brittle inheritance tree. + +## What Worked + +Sharing simulation code was the most important architecture choice. It let the project stay coherent as the client and server evolved. + +The project also validated SDF-2D outside a toy environment. A rendering library is more convincing when it survives a game loop, input, network updates, and mobile constraints. + +## What I Would Change + +I would now spend more effort on observability for synchronization and prediction errors. Multiplayer systems need good visibility into divergence. Without that, debugging becomes a sequence of guesses. + +I would also separate the story of rendering and networking more clearly in the codebase. Both were interesting, but they put different kinds of pressure on the architecture. diff --git a/src/content/posts/greatai-ai-deployment-api.md b/src/content/posts/greatai-ai-deployment-api.md new file mode 100644 index 0000000..c56e850 --- /dev/null +++ b/src/content/posts/greatai-ai-deployment-api.md @@ -0,0 +1,76 @@ +--- +title: Designing an ML Deployment API Around Best Practices +description: How GreatAI tried to make stronger ML deployment habits accessible through a small Python API. +date: 2026-05-09 +projectPeriod: '2022' +thumbnail: + src: ./_assets/great-ai.png + alt: Example Python code using the GreatAI API. +tags: ['ai', 'systems', 'tools'] +selected: true +featuredOrder: 1 +project: great-ai +role: Researcher and framework author +stack: ['Python', 'ML deployment', 'API design'] +scale: 33 deployment best practices, six proposed additions, evaluated with professional data scientists and software engineers +outcome: A Python framework, thesis, and research-backed API design for production-oriented AI deployments +audience: recruiter-relevant +links: + - label: PyPI + type: package + url: https://pypi.org/project/great-ai/ + - label: Project site + type: site + url: https://great-ai.scoutinscience.com + - label: MSc thesis + type: thesis + url: /media/downloads/great-ai-andras-schmelczer.pdf + download: true +media: + - type: image + src: ./_assets/great-ai.png + alt: Example Python code using GreatAI decorators and prediction helpers. + caption: GreatAI's public surface was designed to keep deployment best practices close to the application code. +--- + +GreatAI started from a practical frustration: applying machine learning was becoming easier, but deploying it well was still easy to get wrong. Many failures were not about model architecture. They were about missing metadata, weak versioning, poor reproducibility, untracked inputs, or interfaces that made the right behavior too cumbersome to use. + +My thesis work looked at that gap from two sides. First, I collected and organized AI/ML deployment best practices, including 33 practices and six additions proposed through the research. Then I designed a Python framework that tried to make those practices feel like the natural path rather than an enterprise checklist. + +The result was GreatAI: a deployment-oriented framework with a deliberately small API. The design goal was not to wrap every part of an ML stack. It was to make common deployment concerns visible, automatic where possible, and hard to forget. + +## The Problem + +Deployment quality is often treated as something that happens after model development. That separation creates a bad default. A model can be useful in a notebook, but a deployed AI service also needs traceability, stable interfaces, input/output logging, model metadata, and operational behavior that can be inspected later. + +The hard part is not listing those needs. The hard part is getting busy engineers and data scientists to adopt them without making their work feel slower. + +So the core question became: can a framework implement meaningful deployment practices while keeping the API small enough that people would actually use it? + +## Constraints + +GreatAI had to satisfy two constraints that usually pull in opposite directions. + +It needed to be robust enough to encode deployment practices such as metadata handling, model loading, request tracing, and reproducible prediction interfaces. But it also needed to be approachable enough that the basic use case still looked like ordinary Python. + +That shaped the API. The framework could not demand a new mental model for every project. The deployment behavior had to sit close to the prediction function, because that is where the developer already has context. + +## Design + +The design leaned on decorators and lightweight conventions. The application author should be able to declare the prediction boundary, attach the relevant model and metadata behavior, and let the framework handle repeated operational concerns. + +That is a careful tradeoff. Too much implicit behavior makes systems difficult to debug. Too much explicit setup makes best practices optional in practice, because the path of least resistance is to skip them. GreatAI tried to keep the implicit parts focused on cross-cutting deployment concerns rather than business logic. + +Feedback from professional data scientists and software engineers supported the main premise: ease of use and functionality both matter when people decide whether to adopt deployment tooling. A framework that is technically complete but awkward to use will still fail. + +## What Worked + +The strongest part of the project was treating API design as part of deployment quality. Best practices are not only documentation. They need interface support, defaults, and feedback loops. + +The research also forced the framework to be specific. "Production-ready" is too broad to be useful. A concrete list of deployment practices made it possible to ask which practices can be automated, which ones need explicit developer decisions, and which ones belong outside the framework. + +## What I Would Change + +If I returned to the project now, I would focus more on integration boundaries: how GreatAI should fit into modern observability, model registry, and evaluation workflows without trying to own them. Deployment frameworks age quickly when they become too broad. + +The part I would keep is the central idea: make the right deployment behavior easy enough that it becomes the default. diff --git a/src/content/posts/life-towers-immutable-tries.md b/src/content/posts/life-towers-immutable-tries.md new file mode 100644 index 0000000..426a6ba --- /dev/null +++ b/src/content/posts/life-towers-immutable-tries.md @@ -0,0 +1,60 @@ +--- +title: Syncing State with Immutable Tries +description: How a multi-device life tracking project used trie structure to diff, reconcile, and synchronize goal state. +date: 2026-05-05 +projectPeriod: 'August-September 2019' +thumbnail: + src: ./_assets/towers.png + alt: Life Towers goal tracking interface with tower-like visual structures. +tags: ['systems', 'web', 'tools'] +selected: true +featuredOrder: 3 +project: towers +role: Full-stack author +stack: ['Python', 'Angular', 'State synchronization'] +scale: Multi-device goal and task state shared between clients and a server +outcome: A working synchronization model built around immutable trie properties +audience: recruiter-relevant +links: + - label: Source + type: source + url: https://github.com/schmelczer/life-towers/ + - label: Demo + type: demo + url: https://towers.schmelczer.dev +media: + - type: image + src: ./_assets/towers.png + alt: Screenshot of a life tracking web interface represented with tower-like visual structures. + caption: The visual idea was simple; the useful lesson was the synchronization model behind it. +--- + +Life Towers was a multi-device goal and task tracker with an intentionally visual interface. The surface idea was an aesthetic representation of previous and current goals. The more interesting part was synchronizing state across clients without sending more data than necessary. + +This was not a large distributed system, but it had a real version of a common problem: clients and server drift apart, and the system needs a compact way to compare, reconcile, and update. + +## The Problem + +If a task model is stored as an ordinary mutable object graph, synchronizing it often becomes a choice between sending too much data or writing complicated ad hoc diff logic. + +I wanted a structure where the shape of the data made synchronization easier. The client should be able to compare its state with the server's state, find a difference, reconcile it, and send only the delta. + +## Design + +I used a trie. A trie made the hierarchical shape explicit, and its properties made it easier to reason about differences between stored versions. + +The immutable nature of the structure simplified much of the logic. Instead of mutating arbitrary branches in place, updates could produce new structure with shared unchanged parts. That made reconciliation easier to reason about and reduced the amount of data that needed to move across the network. + +The project also gave me a reason to deepen my Python and Angular knowledge, but the synchronization structure was the main lesson. + +## What Worked + +The biggest win was choosing a data structure that matched the problem. Once the state was represented in a way that made comparison natural, the network protocol became simpler. + +The other useful lesson was that visual products still need a strong internal model. A pleasant interface is fragile if the underlying state is hard to trust. + +## What I Would Change + +Today I would document the sync protocol more formally and add property-based tests around reconciliation. Synchronization code is exactly the kind of code that benefits from generated edge cases. + +I would also separate the visual experiment from the state synchronization story more explicitly. The latter is the part that aged better. diff --git a/src/content/posts/nuclear-cooling-simulation.md b/src/content/posts/nuclear-cooling-simulation.md new file mode 100644 index 0000000..6dab5c0 --- /dev/null +++ b/src/content/posts/nuclear-cooling-simulation.md @@ -0,0 +1,58 @@ +--- +title: Graph Models for a Real-Time Cooling Simulation +description: Simulating a nuclear facility cooling system with graph traversal, matrix solving, Flask, NumPy, and real-time monitoring clients. +date: 2026-05-04 +projectPeriod: 'October-November 2018' +thumbnail: + src: ./_assets/process-simulator.jpg + alt: Cooling system simulator interface with pipes, pumps, and temperature values. +tags: ['simulation', 'systems', 'tools'] +selected: true +featuredOrder: 6 +project: nuclear-simulation +role: Simulation and UI author +stack: ['Python', 'Flask', 'NumPy', 'HTML canvas', 'JavaFX'] +scale: Remote simulation server with multiple monitoring clients and a separate graph editor +outcome: A believable, extensible cooling-system simulation for a cybersecurity challenge context +audience: recruiter-relevant +links: [] +media: + - type: image + src: ./_assets/process-simulator.jpg + alt: Screenshot of the cooling system simulator with pipes, pumps, coolers, and temperature values. + caption: The simulator calculated flow and temperature over graph-based process models. + - type: image + src: ./_assets/process-simulator-input.jpg + alt: Screenshot of the JavaFX graph editor used to define simulator input. + caption: A separate JavaFX editor produced JSON inputs for the simulation backend. +--- + +This project simulated the cooling system of a nuclear facility. It was built for a cybersecurity challenge about PLCs, where participants needed to see the consequences of changing a system state. + +The simulation did not try to be physically complete. It aimed to be cheaply calculated, believable to a non-specialist, scalable enough for the event context, and understandable through a clean GUI. + +## The Problem + +The simulated system needed reactors, coolers, pumps, heat exchangers, drains, sources, and pipes. Those elements had to be configurable, and multiple monitoring clients needed to update in real time from a remote server. + +The key challenge was representing flow and temperature in a way that was simple enough to calculate repeatedly but structured enough to produce plausible behavior. + +## Design + +The system used two graph models. First, water was distributed by traversing the graph of pipes according to pressures generated by pumps. Then, an adjacency matrix was populated from the relations between nodes based on water flow. + +After accounting for base temperatures, heaters, and heat exchangers, the matrix was solved to calculate current node temperatures. Repeating that process advanced the simulation. + +Python handled the backend logic with Flask and NumPy. The monitoring frontend used an HTML5 canvas. A separate JavaFX graph editor let users move nodes, edit element parameters, export JSON, and upload inputs to the backend. + +## What Worked + +The graph/matrix split was a useful modeling boundary. Flow and heat exchange are related, but treating them as separate calculation phases kept the implementation easier to reason about. + +The editor also mattered. A simulation is much more useful when its input is inspectable and editable by people who are not editing source files. + +## What I Would Change + +Today I would formalize the model limitations more clearly. A convincing simulation can be useful, but it should say exactly what it does and does not claim. + +I would also add recorded scenarios and regression tests. Simulation projects are vulnerable to accidental behavior changes that still look plausible on screen. diff --git a/src/content/posts/sdf-2d-ray-tracing.md b/src/content/posts/sdf-2d-ray-tracing.md new file mode 100644 index 0000000..9f084da --- /dev/null +++ b/src/content/posts/sdf-2d-ray-tracing.md @@ -0,0 +1,75 @@ +--- +title: Tile-Based Optimization for 2D SDF Ray Tracing +description: How SDF-2D used signed distance fields, dynamic shaders, and tile-based rendering ideas to make 2D ray tracing run well in the browser. +date: 2026-05-08 +projectPeriod: 'Autumn-Winter 2020' +thumbnail: + src: ./_assets/sdf2d.png + alt: SDF-2D browser demo with soft lighting effects. +tags: ['graphics', 'web', 'systems'] +selected: true +featuredOrder: 2 +project: sdf-2d +role: Library author +stack: ['TypeScript', 'WebGL', 'WebGL2', 'Signed distance fields'] +scale: Browser library with mobile-oriented real-time rendering and reusable demos +outcome: Reusable NPM package and thesis project for efficient 2D SDF rendering +audience: recruiter-relevant +links: + - label: NPM package + type: package + url: https://www.npmjs.com/package/sdf-2d + - label: Demo + type: demo + url: https://sdf2d.schmelczer.dev + - label: Video + type: video + url: https://www.youtube.com/watch?v=K3cEtnZUNR0 + - label: BSc thesis + type: thesis + url: /media/downloads/sdf2d-andras-schmelczer.pdf + download: true +media: + - type: image + src: ./_assets/sdf2d.png + alt: Browser demo page showing SDF-2D scenes rendered with soft lighting effects. + caption: SDF-2D was built as a reusable TypeScript library rather than a single demo. +--- + +SDF-2D was my attempt to make a small, reusable browser library for 2D scenes rendered with ray-tracing techniques. The rendering is based on signed distance fields, where geometry can be represented as functions that return the distance to the nearest surface. + +The interesting part was not the basic idea. Signed distance fields are a known technique. The interesting part was making the approach fast and reusable enough for browser demos, including on mobile devices. + +The project became one half of my BSc thesis, together with the multiplayer game `decla.red`, which used the rendering library in a real interactive setting. + +## The Problem + +Ray tracing and distance-field rendering can produce appealing 2D lighting and reflections, but a straightforward implementation spends too much work per pixel. A browser library also has to deal with device variation: WebGL capabilities, shader limits, mobile GPUs, and the overhead of generating scenes dynamically. + +The goal was not to render one hand-tuned scene. The goal was a library with a simple API, reusable scene definitions, and real-time behavior. + +## Constraints + +The library had to support both WebGL and WebGL2. It had to run acceptably on phones. It had to avoid shipping scene-specific shader code by hand. And it had to expose an API that felt like a rendering library rather than a shader experiment. + +Those constraints pushed the implementation toward generated shaders and capability-aware rendering paths. + +## Design + +The main optimization was inspired by tiled renderers. Instead of treating the entire screen uniformly, the renderer could reason about groups of pixels and avoid unnecessary work where possible. + +That was paired with deferred shading and dynamic shader generation. Dynamic generation mattered because scenes and devices differ. If a feature or operation was not needed for a given scene or device, the generated shader could avoid carrying that cost. + +The API was deliberately kept in TypeScript. That made the library easier to package, document, and reuse in projects that were already browser-first. + +## What Worked + +The project worked best when the library boundary was respected. A good demo can hide a messy implementation. A reusable package cannot. The API had to explain the rendering model without making every user think like a shader compiler. + +The mobile constraint also improved the design. It forced performance work to be structural rather than cosmetic. When a technique works only on a powerful desktop GPU, it is easy to mistake headroom for good architecture. + +## What I Would Change + +Today I would write more instrumentation around shader variants and device behavior. The project had many optimizations, but stronger profiling output would have made tradeoffs easier to explain and compare. + +I would also document the rendering pipeline with diagrams. The ideas are visual, and the explanation should be too. diff --git a/src/content/projects/_assets/ad-astra.jpg b/src/content/projects/_assets/ad-astra.jpg new file mode 100644 index 0000000..890731e Binary files /dev/null and b/src/content/projects/_assets/ad-astra.jpg differ diff --git a/src/data/media/avoid.jpg b/src/content/projects/_assets/avoid.jpg similarity index 100% rename from src/data/media/avoid.jpg rename to src/content/projects/_assets/avoid.jpg diff --git a/src/data/media/simulation.jpg b/src/content/projects/_assets/city-simulation.jpg similarity index 100% rename from src/data/media/simulation.jpg rename to src/content/projects/_assets/city-simulation.jpg diff --git a/src/content/projects/_assets/declared.png b/src/content/projects/_assets/declared.png new file mode 100644 index 0000000..e9b64ae Binary files /dev/null and b/src/content/projects/_assets/declared.png differ diff --git a/src/data/media/forex.jpg b/src/content/projects/_assets/forex.jpg similarity index 100% rename from src/data/media/forex.jpg rename to src/content/projects/_assets/forex.jpg diff --git a/src/content/projects/_assets/great-ai.png b/src/content/projects/_assets/great-ai.png new file mode 100644 index 0000000..da71b20 Binary files /dev/null and b/src/content/projects/_assets/great-ai.png differ diff --git a/src/data/media/led.jpg b/src/content/projects/_assets/leds.jpg similarity index 100% rename from src/data/media/led.jpg rename to src/content/projects/_assets/leds.jpg diff --git a/src/data/media/my-notes.png b/src/content/projects/_assets/my-notes.png similarity index 100% rename from src/data/media/my-notes.png rename to src/content/projects/_assets/my-notes.png diff --git a/src/content/projects/_assets/nuclear-simulation.jpg b/src/content/projects/_assets/nuclear-simulation.jpg new file mode 100644 index 0000000..e2056ac Binary files /dev/null and b/src/content/projects/_assets/nuclear-simulation.jpg differ diff --git a/src/data/media/color.jpg b/src/content/projects/_assets/photo-colour-grader.jpg similarity index 100% rename from src/data/media/color.jpg rename to src/content/projects/_assets/photo-colour-grader.jpg diff --git a/src/data/media/photos.jpg b/src/content/projects/_assets/photos.jpg similarity index 100% rename from src/data/media/photos.jpg rename to src/content/projects/_assets/photos.jpg diff --git a/src/data/media/platform.png b/src/content/projects/_assets/platform-game.png similarity index 100% rename from src/data/media/platform.png rename to src/content/projects/_assets/platform-game.png diff --git a/src/content/projects/_assets/sdf2d.png b/src/content/projects/_assets/sdf2d.png new file mode 100644 index 0000000..9744afb Binary files /dev/null and b/src/content/projects/_assets/sdf2d.png differ diff --git a/src/content/projects/_assets/towers.png b/src/content/projects/_assets/towers.png new file mode 100644 index 0000000..2fd235e Binary files /dev/null and b/src/content/projects/_assets/towers.png differ diff --git a/src/content/projects/ad-astra.md b/src/content/projects/ad-astra.md new file mode 100644 index 0000000..0aae905 --- /dev/null +++ b/src/content/projects/ad-astra.md @@ -0,0 +1,19 @@ +--- +sourceProjectId: ad-astra +title: Ad Astra +description: A tiny embedded game engine and custom PCB built around an ATtiny85V. +thumbnail: + src: ./_assets/ad-astra.jpg + alt: The Ad Astra handheld game running on its OLED display. +period: 'Spring 2020' +sortDate: 2020-04-01 +status: Embedded game engine +technologies: ['C', 'ATtiny85V', 'OLED', 'EEPROM', 'PCB design'] +selected: true +essay: ad-astra-attiny85-game-engine +legacyAnchor: embedded-game-engine +links: + - label: Source + type: source + url: https://github.com/schmelczer/ad_astra +--- diff --git a/src/content/projects/avoid.md b/src/content/projects/avoid.md new file mode 100644 index 0000000..9bfa20c --- /dev/null +++ b/src/content/projects/avoid.md @@ -0,0 +1,18 @@ +--- +sourceProjectId: avoid +title: Avoid +description: A small early web game, kept as an archive of first experiments on the web. +thumbnail: + src: ./_assets/avoid.jpg + alt: Screenshot of the Avoid canvas game. +period: 'January 2018' +sortDate: 2018-01-01 +status: Early web game +technologies: ['JavaScript', 'Canvas'] +selected: false +legacyAnchor: avoid +links: + - label: Demo + type: demo + url: https://schmelczer.dev/avoid +--- diff --git a/src/content/projects/city-simulation.md b/src/content/projects/city-simulation.md new file mode 100644 index 0000000..b278d5d --- /dev/null +++ b/src/content/projects/city-simulation.md @@ -0,0 +1,15 @@ +--- +sourceProjectId: city-simulation +title: City Simulation +description: A Unity traffic simulation where REST-controlled traffic lights could produce visible consequences for a cybersecurity challenge. +thumbnail: + src: ./_assets/city-simulation.jpg + alt: Screenshot of a Unity city traffic simulation. +period: 'July-August 2018' +sortDate: 2018-08-01 +status: Simulation +technologies: ['Unity', 'C#', 'REST API', 'Blender'] +selected: false +legacyAnchor: city-simulation-unity +links: [] +--- diff --git a/src/content/projects/colors.md b/src/content/projects/colors.md new file mode 100644 index 0000000..5e61295 --- /dev/null +++ b/src/content/projects/colors.md @@ -0,0 +1,15 @@ +--- +sourceProjectId: colors +title: Photo Colour Grader +description: A proof-of-concept colour grading UI based on selecting colours and transforming nearby colour ranges. +thumbnail: + src: ./_assets/photo-colour-grader.jpg + alt: Screenshot of a colour grading interface applied to a photograph. +period: 'June 2018' +sortDate: 2018-06-01 +status: UI experiment +technologies: ['JavaScript', 'Canvas', 'Image processing'] +selected: false +legacyAnchor: photo-colour-grader +links: [] +--- diff --git a/src/content/projects/declared.md b/src/content/projects/declared.md new file mode 100644 index 0000000..a1f3194 --- /dev/null +++ b/src/content/projects/declared.md @@ -0,0 +1,26 @@ +--- +sourceProjectId: declared +title: decla.red +description: A team-based mobile multiplayer browser game with shared client/server game logic. +thumbnail: + src: ./_assets/declared.png + alt: The decla.red browser game interface showing a space scene. +period: 'Autumn-Winter 2020' +sortDate: 2020-11-01 +status: Thesis project and browser game +technologies: ['TypeScript', 'Node.js', 'WebSockets', 'Firebase', 'WebGL'] +selected: true +essay: declared-shared-simulation-code +legacyAnchor: multiplayer-mobile-game +links: + - label: Source + type: source + url: https://github.com/schmelczer/decla.red + - label: Demo + type: demo + url: https://decla.red + - label: BSc thesis + type: thesis + url: /media/downloads/sdf2d-andras-schmelczer.pdf + download: true +--- diff --git a/src/content/projects/forex.md b/src/content/projects/forex.md new file mode 100644 index 0000000..1020b2d --- /dev/null +++ b/src/content/projects/forex.md @@ -0,0 +1,15 @@ +--- +sourceProjectId: forex +title: Foreign Exchange Prediction Experiment +description: A frequency-domain prediction experiment using smoothing, differentiation, STFT, extrapolation, and inverse transforms. +thumbnail: + src: ./_assets/forex.jpg + alt: Chart from a foreign exchange prediction experiment. +period: 'Autumn 2019' +sortDate: 2019-10-01 +status: Experiment +technologies: ['Python', 'NumPy', 'SciPy', 'Flask', 'MQL4'] +selected: false +legacyAnchor: predicting-foreign-exchange-rates +links: [] +--- diff --git a/src/content/projects/great-ai.md b/src/content/projects/great-ai.md new file mode 100644 index 0000000..f9935c4 --- /dev/null +++ b/src/content/projects/great-ai.md @@ -0,0 +1,26 @@ +--- +sourceProjectId: great-ai +title: GreatAI +description: A Python framework and research project for making AI deployment best practices easier to adopt. +thumbnail: + src: ./_assets/great-ai.png + alt: Example Python code using the GreatAI API. +period: '2022' +sortDate: 2022-01-01 +status: Research project and framework +technologies: ['Python', 'ML deployment', 'API design'] +selected: true +essay: greatai-ai-deployment-api +legacyAnchor: great-ai-ai-deployment-framework +links: + - label: PyPI + type: package + url: https://pypi.org/project/great-ai/ + - label: Project site + type: site + url: https://great-ai.scoutinscience.com + - label: MSc thesis + type: thesis + url: /media/downloads/great-ai-andras-schmelczer.pdf + download: true +--- diff --git a/src/content/projects/leds.md b/src/content/projects/leds.md new file mode 100644 index 0000000..4ed89f0 --- /dev/null +++ b/src/content/projects/leds.md @@ -0,0 +1,15 @@ +--- +sourceProjectId: leds +title: Lights Synchronized to Music +description: A Raspberry Pi music player that drove RGB LED strips from audio analysis. +thumbnail: + src: ./_assets/leds.jpg + alt: RGB LED strips glowing from a music synchronization project. +period: 'Spring 2016' +sortDate: 2016-04-01 +status: Early hardware/software project +technologies: ['Python', 'NumPy', 'FFT', 'Raspberry Pi', 'Vanilla web'] +selected: false +legacyAnchor: lights-synchronised-to-music +links: [] +--- diff --git a/src/content/projects/my-notes.md b/src/content/projects/my-notes.md new file mode 100644 index 0000000..2ea9c80 --- /dev/null +++ b/src/content/projects/my-notes.md @@ -0,0 +1,18 @@ +--- +sourceProjectId: my-notes +title: My Notes +description: A minimalist Android markdown note organizer and editor powered by Markwon. +thumbnail: + src: ./_assets/my-notes.png + alt: Screenshot of the My Notes Android markdown app. +period: 'November 2019' +sortDate: 2019-11-01 +status: Android app +technologies: ['Android', 'Kotlin/Java', 'Markdown', 'Markwon'] +selected: false +legacyAnchor: my-notes-android-app +links: + - label: Source + type: source + url: https://github.com/schmelczer/my-notes +--- diff --git a/src/content/projects/nuclear-simulation.md b/src/content/projects/nuclear-simulation.md new file mode 100644 index 0000000..4574dee --- /dev/null +++ b/src/content/projects/nuclear-simulation.md @@ -0,0 +1,16 @@ +--- +sourceProjectId: nuclear +title: Cooling System Simulation +description: A graph-based process simulation with a monitoring client and JavaFX input editor. +thumbnail: + src: ./_assets/nuclear-simulation.jpg + alt: Cooling system simulator interface with pipes, pumps, and temperature values. +period: 'October-November 2018' +sortDate: 2018-11-01 +status: Simulation and editor +technologies: ['Python', 'Flask', 'NumPy', 'HTML canvas', 'JavaFX'] +selected: true +essay: nuclear-cooling-simulation +legacyAnchor: simulating-the-cooling-system-of-a-nuclear-facility +links: [] +--- diff --git a/src/content/projects/photos.md b/src/content/projects/photos.md new file mode 100644 index 0000000..65a6518 --- /dev/null +++ b/src/content/projects/photos.md @@ -0,0 +1,18 @@ +--- +sourceProjectId: photos +title: Photo Site Generator +description: A static photo site generated from a directory of images, with automatic resizing to multiple quality settings. +thumbnail: + src: ./_assets/photos.jpg + alt: Screenshot of a generated photography site. +period: 'Summer 2016' +sortDate: 2016-07-01 +status: Static site generator +technologies: ['Webpack', 'Image processing', 'Static site generation'] +selected: false +legacyAnchor: photos +links: + - label: Site + type: site + url: https://photo.schmelczer.dev +--- diff --git a/src/content/projects/platform-game.md b/src/content/projects/platform-game.md new file mode 100644 index 0000000..854b8bb --- /dev/null +++ b/src/content/projects/platform-game.md @@ -0,0 +1,15 @@ +--- +sourceProjectId: platform-game +title: Platform Game +description: An early 3D game in C with SDL 1.2, random maps, destructible voxels, enemies, powerups, and time slowdown. +thumbnail: + src: ./_assets/platform-game.png + alt: Screenshot from an early 3D platform game. +period: 'Autumn 2017' +sortDate: 2017-10-01 +status: Early game project +technologies: ['C', 'SDL 1.2', 'Voxel terrain'] +selected: false +legacyAnchor: platform-game +links: [] +--- diff --git a/src/content/projects/sdf-2d.md b/src/content/projects/sdf-2d.md new file mode 100644 index 0000000..2f5c4cb --- /dev/null +++ b/src/content/projects/sdf-2d.md @@ -0,0 +1,29 @@ +--- +sourceProjectId: sdf2d +title: SDF-2D +description: A browser rendering library for optimized 2D ray tracing with signed distance fields. +thumbnail: + src: ./_assets/sdf2d.png + alt: SDF-2D browser demo with soft lighting effects. +period: 'Autumn-Winter 2020' +sortDate: 2020-12-01 +status: Thesis project and NPM package +technologies: ['TypeScript', 'WebGL', 'WebGL2', 'SDF rendering'] +selected: true +essay: sdf-2d-ray-tracing +legacyAnchor: optimising-2d-ray-tracing +links: + - label: NPM package + type: package + url: https://www.npmjs.com/package/sdf-2d + - label: Demo + type: demo + url: https://sdf2d.schmelczer.dev + - label: Video + type: video + url: https://www.youtube.com/watch?v=K3cEtnZUNR0 + - label: BSc thesis + type: thesis + url: /media/downloads/sdf2d-andras-schmelczer.pdf + download: true +--- diff --git a/src/content/projects/towers.md b/src/content/projects/towers.md new file mode 100644 index 0000000..0c198ca --- /dev/null +++ b/src/content/projects/towers.md @@ -0,0 +1,22 @@ +--- +sourceProjectId: towers +title: Life Towers +description: A multi-device goal tracker where the lasting idea was syncing state with immutable tries. +thumbnail: + src: ./_assets/towers.png + alt: Life Towers goal tracking interface with tower-like visual structures. +period: 'August-September 2019' +sortDate: 2019-09-01 +status: Full-stack web app +technologies: ['Python', 'Angular', 'State synchronization'] +selected: true +essay: life-towers-immutable-tries +legacyAnchor: multi-device-life-tracking +links: + - label: Source + type: source + url: https://github.com/schmelczer/life-towers/ + - label: Demo + type: demo + url: https://towers.schmelczer.dev +--- diff --git a/src/data/media/avoid.png b/src/data/media/avoid.png deleted file mode 100644 index 2836c9d..0000000 Binary files a/src/data/media/avoid.png and /dev/null differ diff --git a/src/data/media/me.jpg b/src/data/media/me.jpg deleted file mode 100644 index 0b68643..0000000 Binary files a/src/data/media/me.jpg and /dev/null differ diff --git a/src/data/media/mp4/forex.mp4 b/src/data/media/mp4/forex.mp4 deleted file mode 100644 index 2714ca2..0000000 Binary files a/src/data/media/mp4/forex.mp4 and /dev/null differ diff --git a/src/data/media/mp4/led.mp4 b/src/data/media/mp4/led.mp4 deleted file mode 100644 index 3932895..0000000 Binary files a/src/data/media/mp4/led.mp4 and /dev/null differ diff --git a/src/data/media/mp4/platform.mp4 b/src/data/media/mp4/platform.mp4 deleted file mode 100644 index 8141ba0..0000000 Binary files a/src/data/media/mp4/platform.mp4 and /dev/null differ diff --git a/src/data/media/mp4/simulation.mp4 b/src/data/media/mp4/simulation.mp4 deleted file mode 100644 index 422a670..0000000 Binary files a/src/data/media/mp4/simulation.mp4 and /dev/null differ diff --git a/src/data/media/webm/forex.webm b/src/data/media/webm/forex.webm deleted file mode 100644 index 7dae7f3..0000000 Binary files a/src/data/media/webm/forex.webm and /dev/null differ diff --git a/src/data/media/webm/led.webm b/src/data/media/webm/led.webm deleted file mode 100644 index e89de89..0000000 Binary files a/src/data/media/webm/led.webm and /dev/null differ diff --git a/src/data/media/webm/platform.webm b/src/data/media/webm/platform.webm deleted file mode 100644 index d46bf8b..0000000 Binary files a/src/data/media/webm/platform.webm and /dev/null differ diff --git a/src/data/media/webm/simulation.webm b/src/data/media/webm/simulation.webm deleted file mode 100644 index 22cacac..0000000 Binary files a/src/data/media/webm/simulation.webm and /dev/null differ diff --git a/src/data/portfolio.ts b/src/data/portfolio.ts deleted file mode 100644 index cf6aea3..0000000 --- a/src/data/portfolio.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Contact } from '../page/contact/contact.html'; -import { Header } from '../page/header/header'; -import { ImageViewer } from '../page/image-viewer/image-viewer'; -import { Link } from '../page/link/link.html'; -import { Main } from '../page/main/main'; -import { PageElement } from '../page/page-element'; -import { TimelineElement } from '../page/timeline-element/timeline-element'; -import { UpArrowButton } from '../page/up-arrow-button/up-arrow-button'; -import cvEnglish from './media/cv-andras-schmelczer.pdf'; -import me from './media/me.jpg'; -import { adAstra } from './projects/ad-astra'; -import { avoid } from './projects/avoid'; -import { citySimulation } from './projects/city-simulation'; -import { declared } from './projects/declared'; -import { forex } from './projects/forex'; -import { greatAi } from './projects/great-ai'; -import { leds } from './projects/leds'; -import { myNotes } from './projects/my-notes'; -import { nuclear } from './projects/nuclear'; -import { nuclearEditor } from './projects/nuclear-editor'; -import { photos } from './projects/photos'; -import { platformGame } from './projects/platform-game'; -import { sdf2d } from './projects/sdf2d'; -import { towers } from './projects/towers'; -import { CV, Email, GitHubLink, LinkedIn } from './shared'; - -const imageViewer = new ImageViewer(); -const contact = new PageElement( - Contact({ - title: 'Get in touch', - links: [ - CV(cvEnglish), - Email('mailto:andras@schmelczer.dev'), - LinkedIn('https://www.linkedin.com/in/andras-schmelczer'), - GitHubLink('https://github.com/schmelczer'), - ], - lastEditText: 'Last modified on ', - }) -); - -const main = new Main( - new Header({ - name: 'Andras Schmelczer', - image: me, - imageAltText: 'a picture of me', - imageViewer, - about: [ - 'With an MSc in Computer Science and more than six years of professional software engineering experience, I can confidently undertake any challenge. My interests span diverse areas, allowing me to design complex — even multidisciplinary — systems with a clear understanding.', - - "I'm passionate about architecting and building large-scale systems, especially in the context of AI/ML. However, in my free time, I also enjoy working with shaders, data visualisation, and sometimes even microcontrollers.", - - `Discover some of my more exciting projects below. And if you'd like to reach out to me, you can find my ${Link( - 'CV and contact details', - '#contact' - )} at the bottom of the page.`, - ], - }), - - ...[ - greatAi, - declared, - sdf2d, - adAstra, - forex, - myNotes, - towers, - nuclear, - nuclearEditor, - citySimulation, - avoid, - platformGame, - photos, - leds, - ].map((p) => new TimelineElement(p, 'Show details', 'Show less', imageViewer)), - - contact -); - -export const portfolio: Array = [ - main, - new UpArrowButton(main, contact, 'go up'), - imageViewer, -]; diff --git a/src/data/projects/ad-astra.ts b/src/data/projects/ad-astra.ts deleted file mode 100644 index 24cfa78..0000000 --- a/src/data/projects/ad-astra.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Video } from '../../page/figure/video/video'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import adAstraPoster from '../media/ad_astra.jpg'; -import adAstraMp4 from '../media/mp4/ad_astra.mp4'; -import adAstraWebM from '../media/webm/ad_astra.webm'; -import { GitHub, videoPosterAltText } from '../shared'; - -export const adAstra: TimelineElementParameters = { - title: 'Embedded game engine', - date: 'Spring 2020', - figure: new Video({ - poster: adAstraPoster, - mp4: adAstraMp4, - webm: adAstraWebM, - altText: videoPosterAltText, - }), - description: - "I've always wanted to combine graphics and microcontrollers, so the obvious next step was making a game engine for the ATTiny85. The video shows how I created an enjoyable game using my engine, literally, from scratch.", - more: [ - "Besides overcoming the hardware's minimal resources, the greatest challenge was designing and manufacturing the PCB; this was also the most rewarding part. The hardware setup is straightforward. Aside from the ATtiny85V, I used a D096-12864-SPI7 OLED display as output and a TSOP4838 IR receiver as input. The circuit runs on 3.3V, so a regulator is also needed. The system is very low-power, with its peak consumption at around 31mW on full brightness, and there is also a standby mode with a current draw of just 1.5mA.", - - "Even though I used C for programming, I tried striking a balance between object-oriented and structured programming to reduce complexity while maintaining performance. For example, I used prototype-based inheritance for the game objects, allowing easy code reuse. Meanwhile, I created a high-performance driver for the display, which uses SIMD techniques to process 4 pixels at once (it's an 8-bit ALU). Thanks to this, the maximum frame times are between 15 and 20 milliseconds at a clock speed of 8 MHz. This means that the lowest FPS during gameplay never dips below 50 FPS.", - - 'There is also fault-tolerant persistent data storage (with atomic commit) utilising the built-in EEPROM for storing game state. To create sprites (which are also stored in the EEPROM), I made a tool that converts PNG-s into C array definitions. These scripts can also be found on GitHub, along with the entire project.', - ], - links: [GitHub('https://github.com/schmelczer/ad_astra')], -}; diff --git a/src/data/projects/avoid.ts b/src/data/projects/avoid.ts deleted file mode 100644 index 2f42fa5..0000000 --- a/src/data/projects/avoid.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Preview } from '../../page/figure/preview/preview'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import avoidPoster from '../media/avoid.png'; -import { Open } from '../shared'; - -export const avoid: TimelineElementParameters = { - title: 'Avoid', - date: 'January 2018', - figure: new Preview( - avoidPoster, - 'https://schmelczer.dev/avoid', - 'A webpage showcasing the SDF-2D project.' - ), - description: - "I recently found my first-ever web game. It's incredibly simple but I killed some time with it, so feel free to try it out but don't judge too harshly.", - - links: [Open('https://schmelczer.dev/avoid')], -}; diff --git a/src/data/projects/city-simulation.ts b/src/data/projects/city-simulation.ts deleted file mode 100644 index 53c1cdb..0000000 --- a/src/data/projects/city-simulation.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Video } from '../../page/figure/video/video'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import citySimulationMp4 from '../media/mp4/simulation.mp4'; -import citySimulationPoster from '../media/simulation.jpg'; -import citySimulationWebM from '../media/webm/simulation.webm'; -import { videoPosterAltText } from '../shared'; - -export const citySimulation: TimelineElementParameters = { - title: 'City simulation — Unity', - date: 'July - August 2018', - figure: new Video({ - poster: citySimulationPoster, - mp4: citySimulationMp4, - webm: citySimulationWebM, - altText: videoPosterAltText, - }), - description: 'I simulated a city where car crashes are more frequent than usual.', - more: [ - 'The state of the traffic lights can be changed through a REST API. Drivers follow the instructions of the traffic lights, so if a mistake is made, there will be collisions. There is also support for displaying tweets on a HUD. This was created as the context for a cybersecurity challenge about PLCs. With the help of this program, the contestants could instantly see the effect of their work.', - - 'An exciting aspect of the project was building it in a server-client architecture. Every decision of the agents is calculated server-side. The real challenge was broadcasting these decisions in a fault-tolerant way using minimal bandwidth.', - - 'It is made with Unity using C# as the scripting language. The models and animations were also made by me using Blender.', - ], - links: [], -}; diff --git a/src/data/projects/colors.ts b/src/data/projects/colors.ts deleted file mode 100644 index 3672675..0000000 --- a/src/data/projects/colors.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import colorsPoster from '../media/color.jpg'; - -export const colors: TimelineElementParameters = { - title: 'Photo colour grader', - date: 'June 2018', - figure: new BorderedImage({ - image: colorsPoster, - alt: 'a picture of the app', - }), - description: 'An innovative (at least I thought so) colour grader web application.', - more: [ - 'The most noteworthy feature of this application is the colour selector UI. This program is only intended as a proof-of-concept, I would have liked to experiment with some ideas and this was the outcome.', - - 'You can select some colours and then apply transformations to the other colours as a function of their distance to the selected colour.', - - 'By clicking on a coloured circle you can change its settings. New circles can be created by clicking in the large circle (and they can also be moved by drag & drop).', - ], - links: [], -}; diff --git a/src/data/projects/declared.ts b/src/data/projects/declared.ts deleted file mode 100644 index 517ac7e..0000000 --- a/src/data/projects/declared.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Preview } from '../../page/figure/preview/preview'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import declaredPoster from '../media/decla-red.png'; -import bscThesis from '../media/sdf2d-andras-schmelczer.pdf'; -import { GitHub, Open, Thesis } from '../shared'; - -export const declared: TimelineElementParameters = { - title: 'Multiplayer mobile game', - date: 'Autumn - Winter 2020', - figure: new Preview(declaredPoster, 'https://decla.red', 'The UI of the video game'), - description: - 'I created a conquest-style online multiplayer browser game using my ray-tracing library (see below). It even runs on mobiles.', - more: [ - 'The scene is set in space. Two large teams have to conquer tiny planets, while they can also shoot at the other team. Points are given based on the number of planets controlled, and the first team which reaches a predefined score wins.', - - "The architecture consists of multiple servers, each of which communicates with 16-32 clients over WebSockets; Firebase is used to reach consensus on the set of active servers. The project uses TypeScript compiled into a website and a Node application. There is a shared library which contains the game logic. This way, both the client and server can link to this library, allowing to use of the same code for calculating the actual next state on the server and client-side-predicting it on the users' devices.", - - 'My favourite part of the project was handling the increasingly complex and heavy-weight game logic. To tackle the former, I decided to borrow inspiration from Smalltalk\'s message passing, including the concept of "messageNotUnderstood". To improve the performance, I implemented k-d trees to decrease the spatial operations\' complexity.', - - 'This game (along with SDF-2D) was my BSc thesis project, so more in-depth information about them can be found in my thesis linked below.', - ], - links: [ - GitHub('https://github.com/schmelczer/decla.red'), - Thesis(bscThesis), - Open('https://decla.red'), - ], -}; diff --git a/src/data/projects/forex.ts b/src/data/projects/forex.ts deleted file mode 100644 index ba2938a..0000000 --- a/src/data/projects/forex.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Video } from '../../page/figure/video/video'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import forexPoster from '../media/forex.jpg'; -import forexMp4 from '../media/mp4/forex.mp4'; -import forexWebM from '../media/webm/forex.webm'; -import { videoPosterAltText } from '../shared'; - -export const forex: TimelineElementParameters = { - title: 'Predicting foreign exchange rates', - date: 'Autumn 2019', - figure: new Video({ - poster: forexPoster, - mp4: forexMp4, - webm: forexWebM, - invertButton: true, - altText: videoPosterAltText, - }), - description: - "The animation shows that my implementation does a somewhat good job predicting (blue graph) the EUR/USD rates (green chart). Of course, I wouldn't trust it with my money.", - more: [ - 'The algorithm is a fancy linear regression in the frequency domain. The steps are the following: smoothing the input values, differentiating, applying a short-time Fourier transformation with overlapped (and Hanning-windowed) windows, extrapolating and then applying the inverse of these transformations to the resulting values.', - - 'This prediction server, written in Python using NumPy, SciPy, and Flask, communicates with an MQL4 client responsible for handling the financial transaction based on this data.', - - "Of course, there is still plenty of room for improvement, but even with this simple algorithm, a sometimes profitable trading strategy is viable. Nonetheless, the project gave me an exciting insight into the world of trading algorithms, the field's complexity, and the fierce competition surrounding it.", - ], - links: [], -}; diff --git a/src/data/projects/great-ai.ts b/src/data/projects/great-ai.ts deleted file mode 100644 index c6ce5ea..0000000 --- a/src/data/projects/great-ai.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import mscThesis from '../media/great-ai-andras-schmelczer.pdf'; -import greatAiPoster from '../media/great-ai.png'; -import { Open, PyPi, Thesis } from '../shared'; - -export const greatAi: TimelineElementParameters = { - title: 'GreatAI — AI deployment framework', - date: '2022', - figure: new BorderedImage({ - image: greatAiPoster, - alt: 'some example code using GreatAI', - isEagerLoaded: true, - }), - description: - 'I investigated an approach for increasing the adoption rate of ML deployment libraries and hence the overall quality of industrial deployments. I did this by simultaneously focusing on providing robust, automated implementations of best practices and an accessible API. One of the outcomes of my research is the GreatAI framework.', - more: [ - 'Applying AI is becoming increasingly more accessible, but many case studies have shown that these applications are often deployed poorly. This may lead to suboptimal performance and the introduction of unintended biases.', - - 'My work presents 33 AI/ML deployment best practices (while introducing six new ones), the difficulties of implementing them, and ways to overcome these challenges. GreatAI helps implement these through an accessible interface.', - - 'Feedback from professional data scientists and software engineers showed that ease of use and functionality are equally important in deciding to adopt deployment technologies, and the proposed framework was rated positively in both dimensions.', - - 'For more details, visit the GitHub page or the paper.', - ], - links: [ - PyPi('https://pypi.org/project/great-ai/'), - Thesis(mscThesis), - Open('https://great-ai.scoutinscience.com'), - ], -}; diff --git a/src/data/projects/leds.ts b/src/data/projects/leds.ts deleted file mode 100644 index 474814b..0000000 --- a/src/data/projects/leds.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Video } from '../../page/figure/video/video'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import ledPoster from '../media/led.jpg'; -import ledMp4 from '../media/mp4/led.mp4'; -import ledWebM from '../media/webm/led.webm'; -import { videoPosterAltText } from '../shared'; - -export const leds: TimelineElementParameters = { - title: 'Lights synchronised to music', - date: 'Spring 2016', - figure: new Video({ - poster: ledPoster, - mp4: ledMp4, - webm: ledWebM, - altText: videoPosterAltText, - }), - description: - 'A full-stack application with a built-in music player, the output of which controls the colour of a couple of RGB LED strips through a Raspberry Pi and some MOSFET-s.', - more: [ - 'This was my first non-trivial project which got finished. Obviously, it is rather far from perfect, but I am still proud that I was able to build it on my own.', - - 'The backend logic is written in Python, and the FFT implementation is provided by NumPy. I also built a simple frontend for accessing the music player and changing the settings using vanilla web development technologies.', - ], - links: [], -}; diff --git a/src/data/projects/my-notes.ts b/src/data/projects/my-notes.ts deleted file mode 100644 index 2b15cb4..0000000 --- a/src/data/projects/my-notes.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import myNotesPoster from '../media/my-notes.png'; -import { GitHub } from '../shared'; - -export const myNotes: TimelineElementParameters = { - title: 'My Notes — Android app', - date: 'November 2019', - figure: new BorderedImage({ - image: myNotesPoster, - alt: 'two screenshots of the application', - }), - description: 'A minimalist Android note organiser and editor powered by Markwon.', - more: [ - 'It is a basic app for creating and filtering markdown notes (based on #hashtags). It was my first exposure to Android development.', - - "All in all, it's not a unique idea, but at least it's functional and has exposed me to a wildly different paradigm than I was used to with full-stack web development. Thus, the knowledge I gained while working on it made its development a worthwhile adventure.", - ], - links: [GitHub('https://github.com/schmelczer/my-notes')], -}; diff --git a/src/data/projects/nuclear-editor.ts b/src/data/projects/nuclear-editor.ts deleted file mode 100644 index 374f817..0000000 --- a/src/data/projects/nuclear-editor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import nuclearEditorPoster from '../media/process-simulator-input.jpg'; - -export const nuclearEditor: TimelineElementParameters = { - title: 'Graph editor — JavaFX', - date: 'October - November 2018', - figure: new BorderedImage({ - image: nuclearEditorPoster, - alt: "a picture of the simulator's UI", - }), - description: - 'An intuitive editor to create and edit input for the nuclear facility simulator (see above).', - more: [ - 'Nodes can be moved with drag & drop gestures. Editing the parameters of elements can be done on the right panel.', - - 'The UI is built with JavaFX. The output can be exported as JSON or directly uploaded to the simulation backend.', - ], - links: [], -}; diff --git a/src/data/projects/nuclear.ts b/src/data/projects/nuclear.ts deleted file mode 100644 index 24436a5..0000000 --- a/src/data/projects/nuclear.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import processSimulatorPoster from '../media/process-simulator.jpg'; - -export const nuclear: TimelineElementParameters = { - title: 'Simulating the cooling system of a nuclear facility', - date: 'October - November 2018', - figure: new BorderedImage({ - image: processSimulatorPoster, - alt: 'a screenshot of the simulator', - }), - description: - 'The temperatures and flow volumes are dynamically calculated by two graph models on a remote server while multiple "monitoring" clients update in real-time.', - more: [ - 'The simulated system is easily extensible and, by default, can contain reactors (heaters), coolers, pumps, heat exchangers, drains, sources, and of course, pipes. With these, simple yet believable configurations can be dynamically defined.', - - 'My project aimed to create a cheaply calculated and, for the average person, convincing simulation which is simply scalable and has a clean GUI. The algorithm takes advantage of graphs and matrices to get to update the state iteratively.', - - 'First, water is distributed by traversing the graph of pipes and according to the pressures generated by the pumps. Then, an adjacency matrix is populated with the relations of the nodes (based on the water flow between them). After considering the base temperatures, heaters, and heat exchangers, the matrix is solved, resulting in the current temperature of each node. This can be repeated many times, and coming from the operations semantic, time-travel is also straightforward to implement.', - - 'Python is used for the backend, along with Flask and NumPy. A REST API facilitates communication between the layers. For rendering the front end, an HTML5 canvas is utilised.', - ], - links: [], -}; diff --git a/src/data/projects/photos.ts b/src/data/projects/photos.ts deleted file mode 100644 index 67bfe34..0000000 --- a/src/data/projects/photos.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import photosPoster from '../media/photos.jpg'; -import { Open } from '../shared'; - -export const photos: TimelineElementParameters = { - title: 'Photos', - date: 'Summer 2016', - figure: new BorderedImage({ - image: photosPoster, - alt: 'a picture of the website', - }), - description: 'A simple webpage where you can view my photos.', - more: [ - "Taking time to appreciate the world around us fills me with joy. That's why I like to go on walks with a camera. I might not end up with great photos. Nonetheless, I usually end up with some inspiration regarding my current or next project.", - - 'As for the webpage, a Webpack script generates the site from the photos in a directory, automatic resizing to multiple quality settings is also part of the pipeline.', - ], - links: [Open('https://photo.schmelczer.dev')], -}; diff --git a/src/data/projects/platform-game.ts b/src/data/projects/platform-game.ts deleted file mode 100644 index 31c4e95..0000000 --- a/src/data/projects/platform-game.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Video } from '../../page/figure/video/video'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import platformMp4 from '../media/mp4/platform.mp4'; -import platformPoster from '../media/platform.png'; -import platformWebM from '../media/webm/platform.webm'; -import { videoPosterAltText } from '../shared'; - -export const platformGame: TimelineElementParameters = { - title: 'Platform game', - date: 'Autumn 2017', - figure: new Video({ - poster: platformPoster, - mp4: platformMp4, - webm: platformWebM, - altText: videoPosterAltText, - }), - description: - 'This was my first proper project. I created an actually fun, 3D game written in pure C with the help of SDL 1.2', - more: [ - 'The maps are randomly generated and fully destroyable voxel-by-voxel. This also allows for creating structures for hiding from flying enemies who chase the player and also can destroy the terrain after merging together and growing larger. After collecting enough powerups, they can shoot and even slow down time in exchange for losing some points.', - - 'I did this as my final project for my Basics of Programming course. Through making this, I learned a lot about pointers after an adequate number of segmentation faults. But it also made me realise my passion for programming.', - ], - links: [], -}; diff --git a/src/data/projects/sdf2d.ts b/src/data/projects/sdf2d.ts deleted file mode 100644 index 31199ac..0000000 --- a/src/data/projects/sdf2d.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Preview } from '../../page/figure/preview/preview'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import sdf2dPoster from '../media/sdf2d.png'; -import { NPM, Open, Youtube } from '../shared'; - -export const sdf2d: TimelineElementParameters = { - title: 'Optimising 2D ray tracing', - date: 'Autumn - Winter 2020', - figure: new Preview( - sdf2dPoster, - 'https://sdf2d.schmelczer.dev', - 'A webpage showcasing the SDF-2D project.' - ), - description: - 'I created the SDF-2D library for efficiently rendering 2D scenes using ray tracing. My solution relies on signed distance fields (SDF-s) and a novel optimisation for 2D SDF rendering inspired by tiled renderers. It even works great on mobiles.', - more: [ - "A multitude of optimisations was needed to achieve compatibility and real-time performance, even on low-end devices. Next to tile-based rendering, these include deferred shading and dynamic shader generation to eliminate unnecessary instructions based on the device's capabilities.", - - 'The result is a reusable library written in TypeScript with a — subjectively — simple and elegant API. The library supports WebGL and WebGL2 and is an easily reusable and extensible NPM package.', - - 'Please check out the GitHub repository, the NPM package, or my thesis (linked above) for more information. Or simply enjoy the mesmerising demo scenes.', - ], - links: [ - NPM('https://www.npmjs.com/package/sdf-2d'), - Youtube('https://www.youtube.com/watch?v=K3cEtnZUNR0'), - Open('https://sdf2d.schmelczer.dev'), - ], -}; diff --git a/src/data/projects/towers.ts b/src/data/projects/towers.ts deleted file mode 100644 index 7938930..0000000 --- a/src/data/projects/towers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BorderedImage } from '../../page/figure/bordered-image/bordered-image'; -import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters'; -import towersPoster from '../media/towers.png'; -import { GitHub, Open } from '../shared'; - -export const towers: TimelineElementParameters = { - title: 'Multi-device life tracking', - date: 'August - September 2019', - figure: new BorderedImage({ - image: towersPoster, - alt: 'a picture of the website', - }), - description: 'An aesthetic representation of your previous and current goals/tasks.', - more: [ - 'This project allowed me to deepen my Python & Angular knowledge. The most exciting part of it — apart from designing and implementing the pleasing visuals — was coming up with its data structure and method of synchronising state between the clients and servers.', - - "In the end, I decided on using a trie. Its properties make it reasonably simple to find the difference between the server's and client's stored versions, reconcile the differences and then only send this delta through the network. Additionally, its immutable nature helped simplify much of the logic.", - ], - links: [ - GitHub('https://github.com/schmelczer/life-towers/'), - Open('https://towers.schmelczer.dev'), - ], -}; diff --git a/src/data/shared.ts b/src/data/shared.ts deleted file mode 100644 index 91ffafd..0000000 --- a/src/data/shared.ts +++ /dev/null @@ -1,26 +0,0 @@ -import cvIcon from '../../static/icons/cv.svg'; -import emailIcon from '../../static/icons/email.svg'; -import gitHubIcon from '../../static/icons/github.svg'; -import linkedInIcon from '../../static/icons/linkedin.svg'; -import openIcon from '../../static/icons/open.svg'; -import packageIcon from '../../static/icons/package.svg'; -import pythonIcon from '../../static/icons/python.svg'; -import youtubeIcon from '../../static/icons/youtube.svg'; -import { ImageAnchorFactory } from '../page/image-anchor/image-anchor.html'; -import { ImageButtonFactory } from '../page/image-button/image-button.html'; - -export const GitHub = ImageButtonFactory(gitHubIcon, 'Open on GitHub'); -export const NPM = ImageButtonFactory(packageIcon, 'Open on NPM'); -export const PyPi = ImageButtonFactory(pythonIcon, 'Open on PyPI'); -export const Open = ImageButtonFactory(openIcon, 'Open in new tab'); -export const Thesis = ImageButtonFactory(cvIcon, 'Download thesis', { - shouldDownload: true, -}); -export const Youtube = ImageButtonFactory(youtubeIcon, 'Open on YouTube'); - -export const CV = ImageAnchorFactory(cvIcon, 'Download my CV', { shouldDownload: true }); -export const GitHubLink = ImageAnchorFactory(gitHubIcon, 'Find me on GitHub'); -export const LinkedIn = ImageAnchorFactory(linkedInIcon, 'Find me on LinkedIn'); -export const Email = ImageAnchorFactory(emailIcon, 'andras@schmelczer.dev'); - -export const videoPosterAltText = 'thumbnail for the video'; diff --git a/src/helper/accessibility.ts b/src/helper/accessibility.ts deleted file mode 100644 index a8509bc..0000000 --- a/src/helper/accessibility.ts +++ /dev/null @@ -1,18 +0,0 @@ -let isSpaceClickActive = false; - -export const addSupportForTabNavigation = () => - document.addEventListener('keydown', (e) => { - if (e.key === ' ') { - isSpaceClickActive = true; - (document.activeElement as HTMLElement)?.click(); - e.preventDefault(); - } - }); - -export const removeUnnecessaryOutlines = () => - document.addEventListener('click', () => { - if (!isSpaceClickActive) { - (document.activeElement as HTMLElement).blur?.(); - } - isSpaceClickActive = false; - }); diff --git a/src/helper/get-height.ts b/src/helper/get-height.ts deleted file mode 100644 index 2258643..0000000 --- a/src/helper/get-height.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const getHeight = (e: HTMLElement): number => { - // ignores margin collapse - const computedStyle = getComputedStyle(e); - - return ( - e.clientHeight + - parseFloat(computedStyle.marginTop) + - parseFloat(computedStyle.marginBottom) + - parseFloat(computedStyle.borderTopWidth) + - parseFloat(computedStyle.borderBottomWidth) - ); -}; diff --git a/src/helper/mix.ts b/src/helper/mix.ts deleted file mode 100644 index d2c194a..0000000 --- a/src/helper/mix.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const mix = (from: number, to: number, q: number): number => - from + (to - from) * q; diff --git a/src/helper/random.ts b/src/helper/random.ts deleted file mode 100644 index 4ede36d..0000000 --- a/src/helper/random.ts +++ /dev/null @@ -1,19 +0,0 @@ -export class Random { - // don't set the seed 0 - public constructor(public seed: number = 42) {} - - public get next(): number { - // result is in [0, 1) - - this.seed = Math.imul(48271, this.seed); - return ((2 ** 31 - 1) & this.seed) / 2 ** 31; - } - - public choose(list: Array): T { - return list[Math.floor(this.inInterval(0, list.length))]; - } - - public inInterval(aClosed: number, bOpen: number): number { - return (bOpen - aClosed) * this.next + aClosed; - } -} diff --git a/src/helper/scroll-to-fragment.ts b/src/helper/scroll-to-fragment.ts deleted file mode 100644 index 98f5432..0000000 --- a/src/helper/scroll-to-fragment.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const scrollToFragment = () => { - // it might be necessary when the page takes too long to load - if (location.hash) { - document.getElementById(location.hash.slice(1))?.scrollIntoView(); - } -}; diff --git a/src/helper/sum.ts b/src/helper/sum.ts deleted file mode 100644 index c75dc64..0000000 --- a/src/helper/sum.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const sum = (list: ArrayLike): number => - // @ts-ignore - Array.prototype.reduce.call(list, (a: number, sum: number) => a + sum, 0); diff --git a/src/helper/title-to-fragment.ts b/src/helper/title-to-fragment.ts deleted file mode 100644 index b57ae90..0000000 --- a/src/helper/title-to-fragment.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const titleToFragment = (title: string): string => - '#' + - encodeURIComponent( - title.toLocaleLowerCase().replace(/&.*?;/g, '').replace(/\W+/g, '-') - ); diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 3fc8c31..0000000 --- a/src/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Portfolio | Andras Schmelczer - - - - - - - - diff --git a/src/index.scss b/src/index.scss deleted file mode 100644 index f41ce9a..0000000 --- a/src/index.scss +++ /dev/null @@ -1,105 +0,0 @@ -@use 'style/fonts'; -@use 'style/vars'; -@use 'style/mixins' as *; - -*, -*::before, -*::after { - margin: 0; - padding: 0; - box-sizing: border-box; - - @media (prefers-reduced-motion) { - transition: none !important; - animation: none !important; - } -} - -html[animations='off'] { - &, - *, - *::before, - *::after { - transition: none !important; - animation: none !important; - } -} - -html { - height: 100%; - overflow: hidden; - -webkit-font-smooth: antialiased; - - @media (min-width: 1000px) and (max-width: 1440px) { - font-size: 0.875rem; - } - - @media (max-width: 999px) { - font-size: 0.8rem; - } - - @media print { - & { - font-size: 0.7rem; - } - } -} - -body { - background-color: var(--background); - transition: background-color linear var(--transition-time); - - padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) - env(safe-area-inset-left); - - height: 100%; - - @media print { - & { - height: auto; - } - } -} - -noscript { - @include square(100%); - @include center-children(); - @include sub-title-font(); -} - -img, -video, -iframe { - user-select: none; - -webkit-user-select: none; -} - -button { - border: none; - background: none; -} - -svg { - stroke: var(--normal-text-color); -} - -p { - @include main-font(); -} - -a { - text-decoration: none; -} - -:focus { - outline: none; - - &:not(:hover) { - outline: var(--accent-color) solid var(--line-width); - } -} - -::selection { - background-color: var(--accent-color); - color: var(--very-light-text-color); -} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 0594805..0000000 --- a/src/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import '../static/no-change/404.html'; -import '../static/no-change/favicons/android-chrome-192x192.png'; -import '../static/no-change/favicons/android-chrome-512x512.png'; -import '../static/no-change/favicons/apple-touch-icon.png'; -import '../static/no-change/favicons/favicon-16x16.png'; -import '../static/no-change/favicons/favicon-32x32.png'; -import '../static/no-change/favicons/favicon.ico'; -import '../static/no-change/favicons/site.webmanifest'; -import '../static/no-change/og-image.jpg'; -import '../static/no-change/robots.txt'; -import { init as plausibleInit } from '@plausible-analytics/tracker'; -import { portfolio } from './data/portfolio'; -import { - addSupportForTabNavigation, - removeUnnecessaryOutlines, -} from './helper/accessibility'; -import { scrollToFragment } from './helper/scroll-to-fragment'; -import './index.scss'; - -plausibleInit({ - domain: 'schmelczer.dev', - endpoint: 'https://stats.schmelczer.dev/status', - autoCapturePageviews: true, - captureOnLocalhost: true, - logging: true, - fileDownloads: true, - hashBasedRouting: true, -}); - -addSupportForTabNavigation(); -removeUnnecessaryOutlines(); -portfolio.forEach((e) => e.attachToDOM(document.body)); -scrollToFragment(); diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro new file mode 100644 index 0000000..c97c3ad --- /dev/null +++ b/src/layouts/Base.astro @@ -0,0 +1,82 @@ +--- +import Footer from '../components/Footer.astro'; +import Header from '../components/Header.astro'; +import { absoluteUrl, site } from '../lib/site'; +import '../styles/global.css'; + +interface Props { + title?: string; + description?: string; + canonicalPath?: string; +} + +const { + title = site.title, + description = site.description, + canonicalPath = Astro.url.pathname, +} = Astro.props; + +const pageTitle = title === site.title ? site.title : `${title} | ${site.name}`; +const canonical = absoluteUrl(canonicalPath); +--- + + + + + + + + + + + + + + + + + + + + + {pageTitle} + + +
+
+ +
+