Skip to content

Site overhaul: static export, design system, 3D stack, SEO, CI - #2

Merged
omercelikdev merged 12 commits into
mainfrom
site-overhaul
Sep 11, 2026
Merged

Site overhaul: static export, design system, 3D stack, SEO, CI#2
omercelikdev merged 12 commits into
mainfrom
site-overhaul

Conversation

@omercelikdev

Copy link
Copy Markdown
Owner

The site overhaul: a static export ready for Cloudflare Workers Builds, a new design system with the signature 3D architecture stack, full SEO, and a maintainable repo.

What changes

  • Static export (output: "export"): plain HTML in ./out, served by wrangler.jsonc as Worker static assets with 404.html. Product cards are fetched from the GitHub API at build time (GITHUB_TOKEN).
  • English-only: no locale prefix, no language switcher. Articles keep their own language (lang attribute, og:locale).
  • Design: OKLCH colour tokens, one type scale, Geist + Newsreader, CSS-first motion (respects reduced motion). Theme follows the system; the toggle overrides it.
  • Architecture stack: calm CSS-3D diagram in the hero and in MDX (<LayerStack>), with accurate hover/tap/keyboard focus.
  • Writing: centred reading layout, table of contents, reading progress, series, tags, callouts, figures, pull quotes, code copy, Giscus comments, RSS.
  • SEO: per-page canonical/Open Graph/X cards, generated .png social cards, JSON-LD (Person, WebSite, ProfilePage, BlogPosting, BreadcrumbList), sitemap, robots, noindex tag pages and 404.
  • Repo hygiene: CI (lint, typecheck, format, tests, static build with output checks), Dependabot, Vitest, Prettier, .nvmrc, .editorconfig, README.

Verified locally

  • npm run check: lint, typecheck, format and 17 tests pass.
  • next build: 23 static routes, all 5 product cards, key files present in out/.

After merging

  • Connect Cloudflare Workers Builds with a GITHUB_TOKEN build variable (see README).
  • Submit the sitemap in Google Search Console.

🤖 Generated with Claude Code

omercelikdev and others added 12 commits September 11, 2026 11:53
- theme toggle: useSyncExternalStore instead of setState in an effect
- typewriter: schedule the phrase advance instead of a synchronous setState
- lang switcher: move the cookie write to module scope, use location.assign
- not-found: next/link instead of a raw anchor
- robots, sitemap, OG images: force-static (+ full locale x slug params)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tools

Foundation
- output: "export" served as Worker assets (wrangler.jsonc, like the
  *.qorpe.com sites); proxy removed, root page picks /en or /tr in the browser
- UI locales cut to English + Turkish, every URL prefixed; each locale is
  canonical for itself, articles canonical in the language they're written in
- products: owner/repo support (goldpath, specdrift, mediant, mockifyr moved
  to qorpe), fetched once per process, loud [products] warnings on failure
- placeholder bio replaced, example posts marked draft, starter SVGs removed

Design
- type scale tokens (text-meta ... text-display) replace arbitrary sizes
- one Label and one Tag style site-wide; PageHeader for inner pages
- CSS-only motion: .intro on load, scroll-driven .reveal; content visible
  without JS, reduced motion respected
- person-first hero with a single, fully server-rendered h1
- mobile header no longer shows Contact twice; skip link; honest mailto form

Writing
- block vs inline code fixed, copy button, file titles, highlighted lines
- heading anchors on hover, TOC from three headings, author card,
  newer/older navigation, Callout and Figure MDX components, footnotes
- JSON-LD (Person/WebSite, BlogPosting), RSS links, Giscus thread per slug

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signature
- ArchitectureStack (src/components/diagram): layers of a system stacked in
  CSS 3D, no WebGL. Tilts with the pointer (TiltScene, eased rAF), opens as
  the page scrolls (scroll-driven --spread), runs a signal down the layers;
  idle sway on touch, a still diagram under reduced motion or without JS
- labels sit on each layer's front edge so every layer reads, not just the top
- hero becomes two columns: the person and headline, then the stack
- the same component is available to essays as <LayerStack><Layer/></LayerStack>

Reading
- article column centred on a three-track grid, TOC in the right margin
- TOC follows the reader (active section highlighted)
- reading progress hairline, CSS scroll-driven
- Newsreader serif for essay titles and pull quotes (<PullQuote>)
- `featured: true` leads the home page's writing section with that essay

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ll SEO

Stack
- one layer in focus at a time: it slides forward and lights up, the layers
  above fade, and its description shows below; plays top to bottom while in
  view (paused off-screen and in background tabs)
- steerable: mouse height over the stack scrubs layers, tap steps forward,
  a legend selects a layer (keyboard included); tilt merged into StackScene
- hero layers get EN/TR descriptions; article LayerStack uses the notes

Standards
- faint text raised to WCAG AA (4.57:1 light, 5.45:1 dark)
- text-control (14px) for buttons, text-field (16px) for inputs (no iOS zoom)
- remaining off-scale sizes and weights removed (404s, logo, portrait)

SEO
- pageMetadata / articleMetadata: per-page canonical, hreflang, og:url,
  og:locale, X cards, RSS link; home title "Ömer Çelik — Software Engineer"
- social cards as real .png routes (og.png, og/<slug>/card.png)
- JSON-LD: Person (+alternateName, knowsAbout), ProfilePage on About,
  BlogPosting (+image, dateModified, publisher) and BreadcrumbList
- robots with max-image-preview, tag pages noindex/follow, theme-color,
  apple-touch-icon, 160px avatar for small portraits
- meta descriptions within ~140 characters
- README: SEO setup and search-engine steps after the first deploy

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hovering the lower layers faded everything above to 16% and stacked their
connectors into a floating blue line. Now the stack fans open at the focus:
layers above lift and draw back, the active layer comes forward and lights
up, and the rest only dim their contents — the structure stays visible. A
single pulse crosses the opened gap into the new layer. Scrubbing gets
hysteresis so resting near a boundary doesn't flicker between two layers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- the active layer now rises above the stack and drifts sideways, so nothing
  covers it and every line on it reads; the others stay in place, dimmed
- connectors removed: the lifted-out card left a blue line pointing upward
- the stack is anchored at its bottom layer, so opening it (scroll or focus)
  only grows upward and keeps a constant gap to the legend
- every layer shows the same three key/value lines
- a stack that starts off-screen begins playing as soon as it's in view

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Back to the open-the-stack focus (layers above lift and draw back, the
focused one comes forward), without what made it messy:

- nothing moves on its own: no autoplay, no idle sway, no background pulse;
  pointing at the stack or the legend opens it, leaving returns it to rest
  (click/tap pins a layer)
- every line on a layer sits along its front edge, the part the open stack
  reveals, so the focused layer's contents read in full
- tilt only while the pointer is over the stack, and a few degrees at most
- the caption becomes the resting state of the description line: two lines
  under the stack instead of three

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hover mapped the stack's height onto five equal bands, which didn't line up
with the layers as drawn in 3D — the focus changed a little above each card.

- invisible hit surfaces, one per layer at its resting position, decide
  which layer is under the pointer; the visible layers stay free to move
- hit surfaces in reverse DOM order: Chrome paints the 3D scene by depth but
  resolves overlapping hit targets by DOM order
- no tilt: the scene turning toward the pointer moved each card away from
  it while you reached for it
- the focused layer steps forward 12px (was 18) so it stays off the next
  layer's hit strip
- tapping a layer on touch pins that layer

Verified with real pointer moves onto each layer's label: every one selects
its own layer, top to bottom and back up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ates

Security
- Next.js 16.2.10 → 16.3.5 (with eslint-config-next) and `npm audit fix`:
  npm audit reports 0 vulnerabilities (was 8, one critical — none of them
  reachable from the static export, but the build machine runs this code)

Checks
- CI (.github/workflows/ci.yml): lint, typecheck, format check, tests, then
  the static build — failing if a product card couldn't be fetched — and a
  check that the key files in out/ exist
- Dependabot: weekly; minor/patch grouped (Next.js + its ESLint config
  together), majors as their own pull requests; GitHub Actions grouped
- Vitest: heading ids vs rehype-slug, tag slugs, SEO helpers, EN/TR message
  parity; scripts typecheck, test, format, format:check, check

Tooling
- Node 24 (.nvmrc, engines >=22.12, @types/node 24), Prettier defaults
  applied to the codebase (essays under content/ excluded), .editorconfig
- .claude/launch.json committed, local agent settings ignored

Cleanup
- unused --hairline token and SiteConfig type removed; SEO internals no
  longer exported
- contact form uses a literal mailto: (Next 16.3 lint rule)
- [locale] segment: dynamicParams = false, so an unknown first segment is a
  404 instead of an error

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Browsers and tools ask for /favicon.ico whatever the page declares; with
only icon.svg the request fell through to the [locale] route (a 404, and an
error in the dev server log). Two RGBA PNG sizes (32, 48) in an ICO — Next's
image pipeline rejects RGB-only PNGs inside an icon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Drop the /en and /tr routes: pages live at the root, no locale prefix,
  no language switcher, no hreflang. next-intl stays for UI copy with a
  fixed English locale, so every page is still statically rendered.
- Articles keep their own language: lang attribute on title, description
  and body; og:locale from the article (tr_TR for Turkish pieces).
- lib/seo: absoluteUrl replaces localeUrl; sitemap and feed list single URLs.
- Theme defaults to the operating system; the toggle still overrides it.
- 404 gets its own title and a consistent noindex.
- Tests, CI output checks and README updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@omercelikdev
omercelikdev merged commit 8d66ccf into main Sep 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant