From 792019885a49826b73058938d9fb2201889de2b3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 19:11:34 +0000 Subject: [PATCH] Restyle to a quiet editorial layout (attardi / nickbytes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Serif lockup, sparse lowercase nav, and proof as inline typographic blocks instead of SaaS cards. Locked headline, proof, and About copy are unchanged. Homepage skim stays hero → trajectory → work → writing/talks → contact. Co-authored-by: Kyle Cesmat --- README.md | 2 +- src/components/ProofCard.astro | 31 ++ src/content.config.ts | 49 +++ src/content/archive/ecommerce-replatform.mdx | 8 + src/content/archive/geospatial-ag.mdx | 8 + src/content/archive/licensing-tool.mdx | 8 + src/content/archive/medical-tracker.mdx | 8 + src/content/archive/speedtest-custom.mdx | 8 + src/content/archive/speedtest-viz.mdx | 8 + src/content/archive/talk-pwa.mdx | 10 + src/content/archive/talk-react-context.mdx | 10 + src/content/archive/talk-realtime-hooks.mdx | 10 + src/content/archive/talk-svelte.mdx | 9 + src/content/archive/trading-terminal.mdx | 8 + src/content/talks/syntax-fm-944.mdx | 11 + src/content/writing/ax-as-product.mdx | 11 + .../writing/enterprise-cursor-ai-rollout.mdx | 11 + .../writing/measuring-ai-productivity.mdx | 11 + .../writing/paved-roads-codegen-load.mdx | 11 + src/content/writing/quality-bars.mdx | 11 + src/data/proof.ts | 66 ++++ src/data/talks.ts | 21 ++ src/layouts/BaseLayout.astro | 55 +++- src/pages/404.astro | 8 +- src/pages/archive.astro | 62 ++++ src/pages/contact.astro | 18 ++ src/pages/index.astro | 82 ++++- src/pages/now.astro | 27 ++ src/pages/talks/[slug].astro | 28 ++ src/pages/talks/index.astro | 31 ++ src/pages/writing/[slug].astro | 25 ++ src/pages/writing/index.astro | 36 +++ src/site.ts | 73 +++++ src/styles/global.css | 291 ++++++++++++++++-- 34 files changed, 1009 insertions(+), 57 deletions(-) create mode 100644 src/components/ProofCard.astro create mode 100644 src/content.config.ts create mode 100644 src/content/archive/ecommerce-replatform.mdx create mode 100644 src/content/archive/geospatial-ag.mdx create mode 100644 src/content/archive/licensing-tool.mdx create mode 100644 src/content/archive/medical-tracker.mdx create mode 100644 src/content/archive/speedtest-custom.mdx create mode 100644 src/content/archive/speedtest-viz.mdx create mode 100644 src/content/archive/talk-pwa.mdx create mode 100644 src/content/archive/talk-react-context.mdx create mode 100644 src/content/archive/talk-realtime-hooks.mdx create mode 100644 src/content/archive/talk-svelte.mdx create mode 100644 src/content/archive/trading-terminal.mdx create mode 100644 src/content/talks/syntax-fm-944.mdx create mode 100644 src/content/writing/ax-as-product.mdx create mode 100644 src/content/writing/enterprise-cursor-ai-rollout.mdx create mode 100644 src/content/writing/measuring-ai-productivity.mdx create mode 100644 src/content/writing/paved-roads-codegen-load.mdx create mode 100644 src/content/writing/quality-bars.mdx create mode 100644 src/data/proof.ts create mode 100644 src/data/talks.ts create mode 100644 src/pages/archive.astro create mode 100644 src/pages/contact.astro create mode 100644 src/pages/now.astro create mode 100644 src/pages/talks/[slug].astro create mode 100644 src/pages/talks/index.astro create mode 100644 src/pages/writing/[slug].astro create mode 100644 src/pages/writing/index.astro create mode 100644 src/site.ts diff --git a/README.md b/README.md index e698f71..c43dbd0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # kylecesmat.com -Personal site. Stack lives on the Cloudflare/Astro PR. **About copy is editable MDX** at `src/pages/about.mdx`. +Personal site. Visual tone is quiet editorial (attardi.org / nickbytes), not a metrics dashboard. **About copy** is editable MDX at `src/pages/about.mdx` — do not rewrite it in layout PRs. ## Stack diff --git a/src/components/ProofCard.astro b/src/components/ProofCard.astro new file mode 100644 index 0000000..5e33a0a --- /dev/null +++ b/src/components/ProofCard.astro @@ -0,0 +1,31 @@ +--- +import type { ProofCardData } from '@/data/proof'; + +interface Props { + card: ProofCardData; +} + +const { card } = Astro.props; +--- + +
+

{card.title}

+ {card.summary ?

{card.summary}

: null} +
    + { + card.metrics.map((metric) => ( +
  • + + {metric.label} + +
  • + )) + } +
+ +
diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..6a9b962 --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,49 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; +import { z } from 'astro/zod'; + +const writing = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/writing' }), + schema: z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + draft: z.boolean().default(false), + placeholder: z.boolean().default(true), + pillar: z.enum([ + 'enterprise-cursor-ai-rollout', + 'ax-as-product', + 'quality-bars', + 'paved-roads-codegen-load', + 'measuring-ai-productivity', + ]), + tags: z.array(z.string()).default([]), + }), +}); + +const talks = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/talks' }), + schema: z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + draft: z.boolean().default(false), + placeholder: z.boolean().default(true), + venue: z.string().optional(), + externalUrl: z.url().optional(), + }), +}); + +const archive = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/archive' }), + schema: z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date(), + kind: z.enum(['project', 'talk']), + venue: z.string().optional(), + externalUrl: z.url().optional(), + }), +}); + +export const collections = { writing, talks, archive }; diff --git a/src/content/archive/ecommerce-replatform.mdx b/src/content/archive/ecommerce-replatform.mdx new file mode 100644 index 0000000..1cdd695 --- /dev/null +++ b/src/content/archive/ecommerce-replatform.mdx @@ -0,0 +1,8 @@ +--- +title: Global eCommerce Re-Platforming +description: NextJS / GraphQL / Node +date: 2019-10-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/geospatial-ag.mdx b/src/content/archive/geospatial-ag.mdx new file mode 100644 index 0000000..4d8e4c7 --- /dev/null +++ b/src/content/archive/geospatial-ag.mdx @@ -0,0 +1,8 @@ +--- +title: Geospatial Analysis for Agriculture +description: Redux / Mapbox / Flow +date: 2016-11-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/licensing-tool.mdx b/src/content/archive/licensing-tool.mdx new file mode 100644 index 0000000..577dbcb --- /dev/null +++ b/src/content/archive/licensing-tool.mdx @@ -0,0 +1,8 @@ +--- +title: Corporate Software Licensing Tool +description: Redux / Node / SQL / Lerna +date: 2018-05-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/medical-tracker.mdx b/src/content/archive/medical-tracker.mdx new file mode 100644 index 0000000..3debd93 --- /dev/null +++ b/src/content/archive/medical-tracker.mdx @@ -0,0 +1,8 @@ +--- +title: Medical Outcome Tracker +description: Devops / SVG / Redux +date: 2017-03-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/speedtest-custom.mdx b/src/content/archive/speedtest-custom.mdx new file mode 100644 index 0000000..ac19164 --- /dev/null +++ b/src/content/archive/speedtest-custom.mdx @@ -0,0 +1,8 @@ +--- +title: Speedtest Custom +description: Websocket / Pattern Library / SQL +date: 2016-06-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/speedtest-viz.mdx b/src/content/archive/speedtest-viz.mdx new file mode 100644 index 0000000..e2a59cb --- /dev/null +++ b/src/content/archive/speedtest-viz.mdx @@ -0,0 +1,8 @@ +--- +title: Speedtest.net Data Visualization +description: SVG / Redux / Mapbox +date: 2017-07-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/archive/talk-pwa.mdx b/src/content/archive/talk-pwa.mdx new file mode 100644 index 0000000..195ee9b --- /dev/null +++ b/src/content/archive/talk-pwa.mdx @@ -0,0 +1,10 @@ +--- +title: Progressive Webapps With ReactJS +description: PHX ReactJS Meetup talk +date: 2017-12-01 +kind: talk +venue: PHX ReactJS Meetup +externalUrl: https://github.com/kylecesmat/phxreactjs-progressive-webapps +--- + +Archived meetup talk. diff --git a/src/content/archive/talk-react-context.mdx b/src/content/archive/talk-react-context.mdx new file mode 100644 index 0000000..a6b9b42 --- /dev/null +++ b/src/content/archive/talk-react-context.mdx @@ -0,0 +1,10 @@ +--- +title: Questionable Decisions w/ React.Context +description: PHX ReactJS Meetup talk +date: 2018-05-01 +kind: talk +venue: PHX ReactJS Meetup +externalUrl: https://github.com/kylecesmat/phxreact-context-may-2018 +--- + +Archived meetup talk. diff --git a/src/content/archive/talk-realtime-hooks.mdx b/src/content/archive/talk-realtime-hooks.mdx new file mode 100644 index 0000000..18a6f02 --- /dev/null +++ b/src/content/archive/talk-realtime-hooks.mdx @@ -0,0 +1,10 @@ +--- +title: Realtime Data With Hooks +description: PHX ReactJS Meetup talk +date: 2019-02-01 +kind: talk +venue: PHX ReactJS Meetup +externalUrl: https://github.com/kylecesmat/phxreact-realtime-hooks +--- + +Archived meetup talk. diff --git a/src/content/archive/talk-svelte.mdx b/src/content/archive/talk-svelte.mdx new file mode 100644 index 0000000..1471616 --- /dev/null +++ b/src/content/archive/talk-svelte.mdx @@ -0,0 +1,9 @@ +--- +title: Introduction to Svelte +description: PhoenixJS Meetup talk +date: 2019-01-01 +kind: talk +venue: PhoenixJS Meetup +--- + +Archived meetup talk. diff --git a/src/content/archive/trading-terminal.mdx b/src/content/archive/trading-terminal.mdx new file mode 100644 index 0000000..59a3323 --- /dev/null +++ b/src/content/archive/trading-terminal.mdx @@ -0,0 +1,8 @@ +--- +title: Realtime Trading Terminal +description: Websocket / Redux / TypeScript +date: 2018-09-01 +kind: project +--- + +Formidable-era consulting project. Details were light because of NDA. diff --git a/src/content/talks/syntax-fm-944.mdx b/src/content/talks/syntax-fm-944.mdx new file mode 100644 index 0000000..935b8be --- /dev/null +++ b/src/content/talks/syntax-fm-944.mdx @@ -0,0 +1,11 @@ +--- +title: 'Is Coinbase Really Writing Half Their Code With AI?' +description: 'Syntax.fm #944 — a conversation about AI coding at scale.' +date: 2026-09-01 +placeholder: true +venue: 'Syntax.fm #944' +--- + +Notes slot for Syntax.fm #944. + +Add the episode URL to `externalUrl` in frontmatter when it should link out. diff --git a/src/content/writing/ax-as-product.mdx b/src/content/writing/ax-as-product.mdx new file mode 100644 index 0000000..f066639 --- /dev/null +++ b/src/content/writing/ax-as-product.mdx @@ -0,0 +1,11 @@ +--- +title: '[TODO: Agent Experience as a product]' +description: '[TODO: empty stub — AX as product]' +date: 2026-01-04 +placeholder: true +pillar: ax-as-product +tags: + - ax-as-product +--- + +Empty stub. Pillar: Agent Experience as a product. diff --git a/src/content/writing/enterprise-cursor-ai-rollout.mdx b/src/content/writing/enterprise-cursor-ai-rollout.mdx new file mode 100644 index 0000000..c369022 --- /dev/null +++ b/src/content/writing/enterprise-cursor-ai-rollout.mdx @@ -0,0 +1,11 @@ +--- +title: '[TODO: Enterprise Cursor / AI rollout]' +description: '[TODO: empty stub — enterprise Cursor/AI rollout]' +date: 2026-01-05 +placeholder: true +pillar: enterprise-cursor-ai-rollout +tags: + - enterprise-cursor-ai-rollout +--- + +Empty stub. Pillar: enterprise Cursor/AI rollout. diff --git a/src/content/writing/measuring-ai-productivity.mdx b/src/content/writing/measuring-ai-productivity.mdx new file mode 100644 index 0000000..42eda86 --- /dev/null +++ b/src/content/writing/measuring-ai-productivity.mdx @@ -0,0 +1,11 @@ +--- +title: '[TODO: Measuring AI productivity]' +description: '[TODO: empty stub — measuring AI productivity]' +date: 2026-01-01 +placeholder: true +pillar: measuring-ai-productivity +tags: + - measuring-ai-productivity +--- + +Empty stub. Pillar: measuring AI productivity. diff --git a/src/content/writing/paved-roads-codegen-load.mdx b/src/content/writing/paved-roads-codegen-load.mdx new file mode 100644 index 0000000..bb7bd36 --- /dev/null +++ b/src/content/writing/paved-roads-codegen-load.mdx @@ -0,0 +1,11 @@ +--- +title: '[TODO: Paved roads under codegen load]' +description: '[TODO: empty stub — paved roads under codegen load]' +date: 2026-01-02 +placeholder: true +pillar: paved-roads-codegen-load +tags: + - paved-roads-codegen-load +--- + +Empty stub. Pillar: paved roads under codegen load. diff --git a/src/content/writing/quality-bars.mdx b/src/content/writing/quality-bars.mdx new file mode 100644 index 0000000..bcbfb7c --- /dev/null +++ b/src/content/writing/quality-bars.mdx @@ -0,0 +1,11 @@ +--- +title: '[TODO: Quality bars]' +description: '[TODO: empty stub — quality bars]' +date: 2026-01-03 +placeholder: true +pillar: quality-bars +tags: + - quality-bars +--- + +Empty stub. Pillar: quality bars. diff --git a/src/data/proof.ts b/src/data/proof.ts new file mode 100644 index 0000000..fde8bab --- /dev/null +++ b/src/data/proof.ts @@ -0,0 +1,66 @@ +export type RedactableMetric = { + label: string; + /** Resume-backed placeholder. Kyle can drop it without a layout change. */ + redactable?: boolean; +}; + +export type ProofCardData = { + id: string; + title: string; + summary?: string; + metrics: RedactableMetric[]; + href: string; + linkLabel: string; +}; + +/** One-paragraph teaser slots. Metrics are placeholders Kyle can redact. */ +export const proofTeaser = { + role: 'SEM, Agent Experience @ Coinbase', + slots: [ + { label: 'founded 0→15 across 3 pods', redactable: true }, + { label: 'AI strategy for 2,200+ eng', redactable: true }, + { label: 'enablement 0%→94%', redactable: true }, + { label: 'Syntax.fm', redactable: false }, + ] satisfies RedactableMetric[], +}; + +export const proofCards: ProofCardData[] = [ + { + id: 'agent-experience-org', + title: 'Agent Experience org', + metrics: [ + { label: '0→15', redactable: true }, + { label: '>30% code change via integrated SDLC tools', redactable: true }, + { label: 'company AI eng strategy', redactable: true }, + ], + href: '/writing/ax-as-product', + linkLabel: '[TODO: case link]', + }, + { + id: 'governed-agent-platform', + title: 'Governed agent platform', + summary: 'Agents, harnesses, sandboxes, MCPs, and skills', + metrics: [ + { label: '~170 MCP servers', redactable: true }, + { label: '~4.8k users', redactable: true }, + { label: '~16k runs/day', redactable: true }, + { label: 'AI review 100% PRs', redactable: true }, + { label: 'merge ~14h→~3.3h', redactable: true }, + ], + href: '/writing/enterprise-cursor-ai-rollout', + linkLabel: '[TODO: case link]', + }, + { + id: 'platform-app-infra', + title: 'Platform / App Infra', + summary: 'Retail Web + Mobile operational excellence', + metrics: [ + { label: 'Code Red errors >98% down', redactable: true }, + { label: 'Buy Flow 26s→2.4s', redactable: true }, + { label: 'Hermes/Expo', redactable: true }, + { label: 'client SLOs', redactable: true }, + ], + href: '/writing/paved-roads-codegen-load', + linkLabel: '[TODO: case link]', + }, +]; diff --git a/src/data/talks.ts b/src/data/talks.ts new file mode 100644 index 0000000..3c781a4 --- /dev/null +++ b/src/data/talks.ts @@ -0,0 +1,21 @@ +export type Talk = { + id: string; + title: string; + venue: string; + featured?: boolean; + href?: string; + notesHref?: string; + linkLabel?: string; +}; + +/** Structured talks list. Syntax.fm #944 stays first. */ +export const talks: Talk[] = [ + { + id: 'syntax-fm-944', + title: 'Is Coinbase Really Writing Half Their Code With AI?', + venue: 'Syntax.fm #944', + featured: true, + notesHref: '/talks/syntax-fm-944', + linkLabel: '[TODO: episode link]', + }, +]; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 82535df..517850c 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,5 +1,6 @@ --- -import '../styles/global.css'; +import { site, nav, hero, headlinePick } from '@/site'; +import '@/styles/global.css'; interface Props { title?: string; @@ -11,12 +12,19 @@ interface Props { } const title = - Astro.props.title ?? Astro.props.frontmatter?.title ?? 'Kyle Cesmat'; + Astro.props.title ?? Astro.props.frontmatter?.title; const description = Astro.props.description ?? Astro.props.frontmatter?.description ?? - 'Personal website of Kyle Cesmat.'; -const canonical = new URL(Astro.url.pathname, Astro.site).href; + site.description; +const homeTitle = + headlinePick === 'placeholder' ? site.name : `${site.name} — ${hero.headline}`; +const pageTitle = title + ? title.includes(site.name) + ? title + : `${title} · ${site.name}` + : homeTitle; +const canonicalUrl = new URL(Astro.url.pathname, site.url).href; const path = Astro.url.pathname.replace(/\/$/, '') || '/'; --- @@ -25,25 +33,48 @@ const path = Astro.url.pathname.replace(/\/$/, '') || '/'; - {title} + {pageTitle} - + + + - - - + + + + + + + + -
- Kyle Cesmat +
+ diff --git a/src/pages/404.astro b/src/pages/404.astro index e7eb742..30a1fce 100644 --- a/src/pages/404.astro +++ b/src/pages/404.astro @@ -1,8 +1,8 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; +import BaseLayout from '@/layouts/BaseLayout.astro'; --- - -

Not found

-

Home

+ +

not found

+

home

diff --git a/src/pages/archive.astro b/src/pages/archive.astro new file mode 100644 index 0000000..55b3574 --- /dev/null +++ b/src/pages/archive.astro @@ -0,0 +1,62 @@ +--- +import { getCollection } from 'astro:content'; +import BaseLayout from '@/layouts/BaseLayout.astro'; + +const items = (await getCollection('archive')).sort( + (a, b) => b.data.date.valueOf() - a.data.date.valueOf(), +); +const projects = items.filter((item) => item.data.kind === 'project'); +const talks = items.filter((item) => item.data.kind === 'talk'); +--- + + +
+

archive

+

+ Formidable consulting projects and Phoenix meetup talks, kept off the primary + nav. Details were always light because of NDAs. +

+
+ +
+

Formidable-era projects

+
    + { + projects.map((entry) => ( +
  • +
    + {entry.data.title} + {entry.data.description} +
    +
  • + )) + } +
+
+ +
+

Meetup talks

+ +
+
diff --git a/src/pages/contact.astro b/src/pages/contact.astro new file mode 100644 index 0000000..babd9cb --- /dev/null +++ b/src/pages/contact.astro @@ -0,0 +1,18 @@ +--- +import BaseLayout from '@/layouts/BaseLayout.astro'; +import { site } from '@/site'; +--- + + +

contact

+ +
diff --git a/src/pages/index.astro b/src/pages/index.astro index cad6de6..15a8d10 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,15 +1,75 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; +import { getCollection } from 'astro:content'; +import BaseLayout from '@/layouts/BaseLayout.astro'; +import ProofCard from '@/components/ProofCard.astro'; +import { proofCards } from '@/data/proof'; +import { talks } from '@/data/talks'; +import { hero, site, writingPillars } from '@/site'; + +const writing = (await getCollection('writing')) + .filter((entry) => !entry.data.draft) + .sort((a, b) => { + const order = new Map(writingPillars.map((pillar, index) => [pillar, index])); + return (order.get(a.data.pillar) ?? 99) - (order.get(b.data.pillar) ?? 99); + }); --- - -

Kyle Cesmat

-

Personal website. Content will land in a follow-up.

-

- kylecesmat@gmail.com - · - GitHub - · - LinkedIn -

+ +
+

{hero.headline}

+

{hero.subhead}

+

Coinbase SEM · formerly Formidable

+
+ +
+

selected work

+
+ {proofCards.map((card) => )} +
+
+ +
+

writing

+ +

all writing

+
+ +
+

talks

+ +

all talks

+
+ +
+

contact

+

+ {site.email} + + + LinkedIn + +

+
diff --git a/src/pages/now.astro b/src/pages/now.astro new file mode 100644 index 0000000..93bbae8 --- /dev/null +++ b/src/pages/now.astro @@ -0,0 +1,27 @@ +--- +import BaseLayout from '@/layouts/BaseLayout.astro'; +--- + + +
+

now

+

+ [TODO: now — last updated] +

+
+ +
+

Focusing

+

+ [TODO: now — focusing] +

+

Reading

+

+ [TODO: now — reading] +

+

Location

+

+ [TODO: now — location] +

+
+
diff --git a/src/pages/talks/[slug].astro b/src/pages/talks/[slug].astro new file mode 100644 index 0000000..77a44bb --- /dev/null +++ b/src/pages/talks/[slug].astro @@ -0,0 +1,28 @@ +--- +import { getCollection, render } from 'astro:content'; +import BaseLayout from '@/layouts/BaseLayout.astro'; +import { talks as talkList } from '@/data/talks'; + +export async function getStaticPaths() { + const notes = await getCollection('talks'); + return notes.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); +} + +const { entry } = Astro.props; +const { Content } = await render(entry); +const listed = talkList.find((talk) => talk.id === entry.id); +const title = listed?.title ?? entry.data.title; +const venue = listed?.venue ?? entry.data.venue; +--- + + +
+

talk

+

{title}

+

{venue}{entry.data.placeholder ? ' · notes slot' : ''}

+ +
+
diff --git a/src/pages/talks/index.astro b/src/pages/talks/index.astro new file mode 100644 index 0000000..f7adcdd --- /dev/null +++ b/src/pages/talks/index.astro @@ -0,0 +1,31 @@ +--- +import BaseLayout from '@/layouts/BaseLayout.astro'; +import { talks } from '@/data/talks'; +--- + + +
+

talks

+
+ +
+ +
+
diff --git a/src/pages/writing/[slug].astro b/src/pages/writing/[slug].astro new file mode 100644 index 0000000..341795c --- /dev/null +++ b/src/pages/writing/[slug].astro @@ -0,0 +1,25 @@ +--- +import { getCollection, render } from 'astro:content'; +import BaseLayout from '@/layouts/BaseLayout.astro'; + +export async function getStaticPaths() { + const writing = await getCollection('writing'); + return writing.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); +} + +const { entry } = Astro.props; +const { Content } = await render(entry); +const { title, description, placeholder } = entry.data; +--- + + +
+

writing

+

{title}

+

{placeholder ? 'draft' : description}

+ +
+
diff --git a/src/pages/writing/index.astro b/src/pages/writing/index.astro new file mode 100644 index 0000000..d7ab61a --- /dev/null +++ b/src/pages/writing/index.astro @@ -0,0 +1,36 @@ +--- +import { getCollection } from 'astro:content'; +import BaseLayout from '@/layouts/BaseLayout.astro'; +import { writingPillars } from '@/site'; + +const writing = (await getCollection('writing')) + .filter((entry) => !entry.data.draft) + .sort((a, b) => { + const order = new Map(writingPillars.map((pillar, index) => [pillar, index])); + return (order.get(a.data.pillar) ?? 99) - (order.get(b.data.pillar) ?? 99); + }); +--- + + +
+

writing

+
+ +
+ +
+
diff --git a/src/site.ts b/src/site.ts new file mode 100644 index 0000000..91d614d --- /dev/null +++ b/src/site.ts @@ -0,0 +1,73 @@ +/** + * Headline pick: `'placeholder' | 'A' | 'B' | 'C'`. + * Kyle chooses later. Do not set A/B/C until he does. + * Do not use “Software Engineer” as the headline. + */ +export type HeadlinePick = 'placeholder' | 'A' | 'B' | 'C'; + +export const headlinePick: HeadlinePick = 'placeholder'; + +/** Labeled options for Kyle. Not shown until `headlinePick` is A, B, or C. */ +export const headlineOptions = { + A: { + headline: 'Building Agent Experience for enterprise engineering orgs', + subhead: + 'SEM at Coinbase leading Agent Experience. Brought Cursor in early; shipping AI coding workflows that raise quality bars, not just autocomplete.', + }, + B: { + headline: 'Platform and DX leadership for the AI coding era', + subhead: + 'I lead teams that turn AI coding tools into governed, measurable developer productivity — from enterprise rollout to agent-native workflows.', + }, + C: { + headline: '[TODO: C — Head-of-X operator framing]', + subhead: + 'Head of Platform / DX / AI Engineering. Coinbase SEM, Agent Experience. Early Cursor enterprise. Syntax.fm on AI coding at scale.', + }, +} as const; + +const placeholderHero = { + headline: '[TODO: headline — set headlinePick to A, B, or C in src/site.ts]', + subhead: '[TODO: subhead]', +} as const; + +export const hero = + headlinePick === 'placeholder' ? placeholderHero : headlineOptions[headlinePick]; + +export const site = { + name: 'Kyle Cesmat', + url: 'https://kylecesmat.com', + email: 'kylecesmat@gmail.com', + linkedin: 'https://www.linkedin.com/in/kylecesmat', + location: 'Denver, CO', + jobTitle: 'Senior Engineering Manager, Agent Experience', + worksFor: 'Coinbase', + description: + 'Kyle Cesmat — Agent Experience, platform engineering, developer experience, and AI engineering leadership.', + keywords: + 'Agent Experience, platform engineering, developer experience, DX, AI engineering leadership, Coinbase', + social: [ + { label: 'LinkedIn', href: 'https://www.linkedin.com/in/kylecesmat' }, + { label: 'GitHub', href: 'https://github.com/kylecesmat' }, + { label: 'Twitter', href: 'https://twitter.com/kylecesmat' }, + { label: 'Instagram', href: 'https://www.instagram.com/kylecesmat' }, + { label: 'Unsplash', href: 'https://unsplash.com/@kylecesmat' }, + ], +} as const; + +export const writingPillars = [ + 'enterprise-cursor-ai-rollout', + 'ax-as-product', + 'quality-bars', + 'paved-roads-codegen-load', + 'measuring-ai-productivity', +] as const; + +export type WritingPillar = (typeof writingPillars)[number]; + +export const nav = [ + { label: 'about', href: '/about' }, + { label: 'writing', href: '/writing' }, + { label: 'talks', href: '/talks' }, + { label: 'contact', href: '/contact' }, +] as const; diff --git a/src/styles/global.css b/src/styles/global.css index 523d2df..fc9a8c3 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,3 +1,14 @@ +:root { + --bg: #f7f6f2; + --ink: #1c1b19; + --muted: #6a6760; + --line: #e4e1d8; + --max: 36rem; + --space: clamp(1.25rem, 4vw, 2.25rem); + --serif: 'Iowan Old Style', Palatino, 'Palatino Linotype', Georgia, 'Times New Roman', serif; + --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif; +} + *, *::before, *::after { @@ -5,64 +16,286 @@ } html { - color-scheme: light; + background: var(--bg); + color: var(--ink); + font-family: var(--serif); + font-size: 112.5%; + line-height: 1.55; + -webkit-text-size-adjust: 100%; } body { margin: 0 auto; - max-width: 40rem; - padding: 1.5rem; - font-family: system-ui, sans-serif; - line-height: 1.5; - color: #111; - background: #fff; + max-width: var(--max); + min-height: 100vh; + padding: var(--space); + display: flex; + flex-direction: column; +} + +img { + max-width: 100%; + height: auto; +} + +a { + color: inherit; + text-underline-offset: 0.14em; +} + +a:hover { + text-decoration-thickness: 2px; +} + +:focus-visible { + outline: 2px solid var(--ink); + outline-offset: 3px; } .skip-link { position: absolute; - left: -9999px; + left: var(--space); + top: -4rem; + z-index: 20; + background: var(--ink); + color: var(--bg); + padding: 0.35rem 0.65rem; + font-family: var(--sans); + font-size: 0.85rem; } .skip-link:focus { - left: 1rem; - top: 1rem; + top: 0.75rem; } -header { +.site-header { display: flex; - justify-content: space-between; + flex-wrap: wrap; align-items: baseline; - gap: 1rem; - margin-bottom: 2rem; + justify-content: space-between; + gap: 0.5rem 1.5rem; + margin-bottom: 3.25rem; +} + +.wordmark { + font-size: 1.05rem; + font-weight: 500; + letter-spacing: -0.02em; + text-decoration: none; } nav { display: flex; - gap: 1rem; + flex-wrap: wrap; + gap: 0.15rem 0.95rem; + font-family: var(--sans); + font-size: 0.82rem; } -a { - color: #111; +nav a { + color: var(--muted); + text-decoration: none; +} + +nav a[aria-current='page'], +.wordmark[aria-current='page'] { + color: var(--ink); +} + +nav a:hover { + color: var(--ink); + text-decoration: underline; +} + +main { + flex: 1; +} + +.site-footer { + margin-top: 3.5rem; + padding-top: 1rem; + border-top: 1px solid var(--line); + color: var(--muted); + font-family: var(--sans); + font-size: 0.82rem; +} + +.site-footer a { + color: inherit; + text-decoration: none; +} + +.site-footer a:hover { + color: var(--ink); + text-decoration: underline; +} + +.hero { + margin-bottom: 0.25rem; +} + +.hero h1, +.page-title, +.prose h1 { + font-size: clamp(1.7rem, 4.8vw, 2.35rem); + font-weight: 500; + letter-spacing: -0.03em; + line-height: 1.15; + margin: 0 0 0.85rem; +} + +.identity { + margin: 0 0 0.65rem; + font-family: var(--sans); + font-size: 0.78rem; + color: var(--muted); +} + +.subhead, +.trajectory { + margin: 0; + color: var(--muted); + font-size: 1.02rem; +} + +.trajectory { + margin-top: 1.1rem; } -nav a[aria-current='page'] { +.section { + margin-top: 2.75rem; +} + +.section h2, +.prose h2 { + font-family: var(--sans); + font-size: 0.78rem; + font-weight: 500; + letter-spacing: 0.02em; + color: var(--muted); + margin: 0 0 1rem; +} + +.section p, +.prose p { + margin: 0 0 1rem; +} + +.proof-list { + display: flex; + flex-direction: column; + gap: 1.75rem; +} + +.proof-block h3 { + font-size: 1.15rem; + font-weight: 500; + letter-spacing: -0.02em; + margin: 0 0 0.3rem; +} + +.proof-block__summary { + margin: 0 0 0.45rem; + color: var(--muted); +} + +.proof-block__metrics { + margin: 0; + padding: 0; + list-style: none; + color: var(--muted); + font-size: 0.95rem; +} + +.proof-block__metrics li { + display: inline; +} + +.proof-block__metrics li + li::before { + content: ' · '; +} + +.proof-block__link { + margin: 0.45rem 0 0; + font-family: var(--sans); + font-size: 0.82rem; + color: var(--muted); +} + +.proof-block__link a { + color: inherit; +} + +.metric--redactable { + text-decoration: underline dotted; + text-underline-offset: 0.16em; +} + +.placeholder { + font-family: var(--sans); + font-size: 0.88em; + color: var(--muted); +} + +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.list li + li { + margin-top: 0.95rem; +} + +.list a, +.list .list-row { + display: grid; + gap: 0.12rem; text-decoration: none; - font-weight: 600; } -main h1 { - font-size: 1.75rem; - font-weight: 600; - line-height: 1.2; +.list a:hover .list-title { + text-decoration: underline; } -main h2 { +.list-title { + font-weight: 500; +} + +.list-meta, +.prose .meta { + color: var(--muted); + font-family: var(--sans); + font-size: 0.82rem; +} + +.more { + margin-top: 1.1rem; + font-family: var(--sans); + font-size: 0.82rem; +} + +.more a { + color: var(--muted); +} + +.prose h2 { margin-top: 2rem; - font-size: 1.125rem; - font-weight: 600; } -main p, -main ul { - margin: 1em 0; +.prose ul { + margin: 0 0 1rem; + padding-left: 1.1rem; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; }