Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"stylelint.validate": ["astro"]
"stylelint.validate": ["astro", "css"]
}
5 changes: 3 additions & 2 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Features } from "lightningcss";
import { existsSync, readFileSync } from "node:fs";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug";
import { markdownConfig } from "./markdown.config";
import { markdownConfig, markdownProcessorConfig } from "./markdown.config";

// https://astro.build/config
const siteUrl = process.env.SITE_URL ?? "https://omerbalyali.com";
Expand Down Expand Up @@ -34,8 +34,9 @@ const localHttps = () => {

export default defineConfig({
markdown: {
...markdownConfig,
processor: unified({
...markdownConfig,
...markdownProcessorConfig,
rehypePlugins: [
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: "wrap", test: ["h2", "h3", "h4", "h5", "h6"] }],
Expand Down
8 changes: 6 additions & 2 deletions markdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
transformerNotationHighlight,
transformerRemoveLineBreak,
} from "@shikijs/transformers";
import type { UnifiedProcessorOptions } from "@astrojs/markdown-remark";
import type { AstroUserConfig } from "astro";
import { remarkReadingTime } from "./src/lib/remark-reading-time";

Expand Down Expand Up @@ -95,7 +96,6 @@ function transformerWrapCodeLines(): ShikiTransformer {
}

export const markdownConfig = {
remarkPlugins: [remarkReadingTime],
shikiConfig: {
themes: {
light: "github-light",
Expand All @@ -111,4 +111,8 @@ export const markdownConfig = {
transformerWrapCodeLines(),
],
},
} satisfies MarkdownConfig;
} satisfies Pick<MarkdownConfig, "shikiConfig">;

export const markdownProcessorConfig = {
remarkPlugins: [remarkReadingTime],
} satisfies UnifiedProcessorOptions;
Binary file added src/assets/images/works/isometry/website.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/components/app/NavBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { pathname } = Astro.url;

const links = [
{ href: "/", label: "Home", priority: 3, isHome: true },
// { href: "/works/", label: "Works", priority: 2 },
{ href: "/works/", label: "Works", priority: 2 },
{ href: "/writing/", label: "Writing", priority: 2 },
{ href: "/about/", label: "About", priority: 1 },
{
Expand Down Expand Up @@ -106,7 +106,8 @@ const menuLinks = links.filter((link) => !("isHome" in link && link.isHome));
position: fixed;
width: 100%;
height: var(--t-space-20);
background: linear-gradient(to top, var(--t-color-background-page), transparent);
mask: linear-gradient(to top, var(--t-color-background-page), transparent);
backdrop-filter: blur(2px);
z-index: 5;
pointer-events: none;
inset-block-end: 0;
Expand All @@ -128,7 +129,7 @@ const menuLinks = links.filter((link) => !("isHome" in link && link.isHome));
:where(&) {
--s-inset-inline: 0;
--s-margin-inline-end: auto;
--s-opacity: 0;
--s-opacity: 1;
--s-transition: none;
}

Expand All @@ -147,8 +148,7 @@ const menuLinks = links.filter((link) => !("isHome" in link && link.isHome));
-webkit-user-select: none;

&[data-ready] {
--s-opacity: 1;
--s-transition: opacity var(--t-duration-base) var(--t-ease-out);
--s-transition: none;
}

@media (width <= 319px) {
Expand Down
86 changes: 86 additions & 0 deletions src/layouts/WorkDetailNew.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
import { getOgImageMetadata } from "../lib/og-pages";
import type { PageMetadata } from "../lib/seo";
import { SITE } from "../site";
import Layout from "./Layout.astro";

const { frontmatter } = Astro.props;
const ogMetadata = frontmatter.ogImage
? { ogImage: frontmatter.ogImage }
: getOgImageMetadata(Astro.url.pathname);
const metadata: PageMetadata = {
title: frontmatter.title,
description: frontmatter.description ?? SITE.sections.works.description,
canonicalURL: Astro.url.pathname,
...ogMetadata,
type: "article",
};
---

<Layout metadata={metadata}>
<article>
<header>
<h1>{frontmatter.title}</h1>
<time datetime={frontmatter.year}>{frontmatter.year}</time>
<p class="description">{frontmatter.description}</p>
</header>
<section class="content prose">
<slot />
</section>
</article>

<style>
header {
margin-block-end: var(--t-space-6);

h1 {
font-size: var(--t-font-size-xlarge);
font-weight: var(--t-font-weight-medium);
}

time {
display: block;
font-size: var(--t-font-size-small);
font-weight: var(--t-font-weight-medium);
color: oklch(from currentColor calc(l + 0.3) c h);
}

.description {
margin-block: var(--t-space-6) var(--t-space-16);
font-size: var(--t-font-size-regular);
font-weight: var(--t-font-weight-medium);
}
}

dl {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 22ch), 1fr));
gap: var(--t-space-4);
margin-block: var(--t-space-8);
padding: var(--t-space-6);
margin-inline: calc(var(--t-space-4) * -1);
background-color: color-mix(in oklch, currentColor 2%, transparent);
border: var(--t-border-thin) solid;
border-color: color-mix(in oklch, currentColor 4%, transparent);
border-radius: var(--t-radius-medium);

div {
display: flex;
flex-direction: column;
gap: var(--t-space-1);
}

dt {
font-size: var(--t-font-size-xsmall);
font-weight: var(--t-font-weight-semibold);
text-transform: uppercase;
color: color-mix(in oklch, currentColor 70%, transparent);
}

dd {
font-weight: var(--t-font-weight-medium);
text-wrap: pretty;
}
}
</style>
</Layout>
12 changes: 0 additions & 12 deletions src/pages/_works/idea-compass.mdx

This file was deleted.

12 changes: 0 additions & 12 deletions src/pages/_works/statefulcss.mdx

This file was deleted.

3 changes: 1 addition & 2 deletions src/pages/_works/index.astro → src/pages/works/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ const metadata = {
<Layout metadata={metadata}>
<h1>Works</h1>
<ul>
<li><a href="/works/statefulcss/">Stateful CSS</a></li>
<li><a href="/works/idea-compass/">IDEA Compass</a></li>
<li><a href="/works/isometry/">Isometry</a></li>
</ul>
</Layout>

Expand Down
26 changes: 26 additions & 0 deletions src/pages/works/isometry.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
layout: ../../layouts/WorkDetailNew.astro
title: Isometry
year: 2026
description: Isometry is a concept for a developer tool that helps teams define, validate, and maintain design system components through machine-readable contracts. It gives components a shared specification layer that can be used by humans, CI pipelines, and AI workflows.
---

import { Image } from "astro:assets";
import website from "../../assets/images/works/isometry/website.png";

## Problem

Design systems often rely on documentation, conventions, and manual review to keep components consistent. As teams scale, components drift: states go missing, accessibility rules become inconsistent, and implementation details fall out of sync with the original design intent. AI-generated code makes this faster, but not necessarily safer, because output still needs a reliable way to be checked against the system’s standards.

## Context

The project explores how design systems could become more enforceable and machine-readable, similar to how APIs use OpenAPI or infrastructure uses Terraform. Instead of treating component behavior, accessibility, and structure as scattered knowledge across docs and code, Isometry proposes a dedicated contract format that can sit beside the implementation and guide the development workflow.

## Solution

Isometry introduces a spec-first workflow for design system components. Teams define states, accessibility requirements, structural rules, variants, and constraints in an .isometry.yaml contract, then use the CLI to validate implementations locally or in CI. The same contract can support generation, review, documentation, and AI-assisted workflows, helping teams keep components aligned from spec to release.

<br />
<br />

<Image src={website} alt="Isometry website" class="bleed" data-bleed="wide" data-bleed-rounded />
1 change: 1 addition & 0 deletions src/styles/theme/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
/* Layout */
[data-theme] {
--t-app-page-max-width: 54rem;
--t-app-content-wide-max-width: 90rem;
--t-app-page-gutter: var(--t-space-6);

--t-app-container-gutter-inline: max(
Expand Down
42 changes: 42 additions & 0 deletions src/styles/utilities.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* stylelint-disable custom-property-pattern */

.app-container {
margin-inline: var(--t-app-container-gutter-inline);
}
Expand All @@ -16,3 +18,43 @@
white-space: nowrap;
width: 1px;
}

.bleed {
:where(&) {
--_bleed-gutter-inline: var(--t-app-container-gutter-inline);
--_bleed-page-inline-size: calc(100% + var(--_bleed-gutter-inline) + var(--_bleed-gutter-inline));
--_bleed-page-safe-inline-size: calc(100svw - var(--t-app-page-gutter) - var(--t-app-page-gutter));
--_bleed-wide-inline-size: min(var(--_bleed-page-safe-inline-size), var(--t-app-content-wide-max-width));
--s-inline-size: var(--_bleed-page-inline-size);
--s-max-inline-size: none;
--s-margin-inline: calc(var(--_bleed-gutter-inline) * -1);
--s-overflow: visible;
--s-border-radius: 0;
}

inline-size: var(--s-inline-size);
max-inline-size: var(--s-max-inline-size);
margin-inline: var(--s-margin-inline);
overflow: var(--s-overflow);
border-radius: var(--s-border-radius);

&[data-bleed="page"] {
--s-inline-size: var(--_bleed-page-inline-size);
--s-margin-inline: calc(var(--_bleed-gutter-inline) * -1);
}

&[data-bleed="wide"] {
--s-inline-size: var(--_bleed-wide-inline-size);
--s-margin-inline: calc((100% - var(--_bleed-wide-inline-size)) / 2);
}

&[data-bleed-rounded] {
--s-overflow: clip;
--s-border-radius: var(--t-radius-large);
}
}

.bleed > :where(img, picture, video, canvas, svg) {
inline-size: 100%;
max-inline-size: none;
}
8 changes: 5 additions & 3 deletions tests/e2e/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ test.describe("primary navigation", () => {
await page.goto("/writing/");

await page.setViewportSize({ width: 500, height: 700 });
await expect.poll(() => visiblePrimaryLinks(page)).toEqual(["Home", "Writing", "About"]);
await expect.poll(() => visiblePrimaryLinks(page)).toEqual(["Home", "Works", "Writing", "About"]);
await moreButton(page).click();
await expect.poll(() => visibleMenuLinks(page)).not.toContain("Writing");
await expect.poll(() => visibleMenuLinks(page)).not.toContain("About");
Expand All @@ -78,12 +78,14 @@ test.describe("primary navigation", () => {
).toBe(true);

await page.setViewportSize({ width: 389, height: 700 });
await expect.poll(() => visiblePrimaryLinks(page)).toEqual(["Home", "Writing"]);
await expect.poll(() => visiblePrimaryLinks(page)).toEqual(["Home", "Works", "Writing"]);
await expect.poll(() => visibleMenuLinks(page)).toContain("About");

await page.setViewportSize({ width: 319, height: 700 });
await expect.poll(() => visiblePrimaryLinks(page)).toEqual(["Home"]);
await expect.poll(() => visibleMenuLinks(page)).toEqual(expect.arrayContaining(["Writing", "About"]));
await expect
.poll(() => visibleMenuLinks(page))
.toEqual(expect.arrayContaining(["Works", "Writing", "About"]));

await expect(nav(page).getByRole("link", { name: "Writing" }).last()).toHaveAttribute(
"aria-current",
Expand Down
Loading