Portfolio ra mắt hồi tháng 5 còn hai thứ làm tôi bứt rứt. Panel chat biến mất khi bấm vào nó. Và toàn bộ site trông giống mọi landing page do AI sinh ra — gradient tím trên nền mesh đen, card glass blur, đủ bộ. Tôi mất một cuối tuần để sửa cả hai, và hóa ra hai vấn đề ấy bện chung một gốc: tôi đã ngừng để ý tới mặc định của chính mình.

Tại sao tôi vứt glass đi

Thiết kế cũ dựa trên một utility .glass được pha bằng color-mix của foreground ở 5% opacity. Trên nền tối thì card trông bạc thếch. Trên nền sáng thì card vô hình. Cái mesh gradient phía sau thì tím-và-cyan đúng đăng-ký màu mà mọi công cụ thiết kế AI ship mặc định. Tiêu đề dùng font tôi chưa từng chọn. Thẩm mỹ ấy là kết quả của không quyết định.

Tôi dựng lại quanh ba ràng buộc: serif display, gạch hairline, một accent duy nhất. Fraunces cho tiêu đề, Geist cho body, JetBrains Mono cho số. Một accent màu hổ phách (ember). Không blur, không shadow. Bản sắc thị giác giờ là một tư thế — biên tập, có chủ ý, đánh số theo section và volume — chứ không phải hiệu ứng đặc biệt.

Thiết kế là một. Bộ môn không thay đổi.

— Massimo Vignelli

Chuyển chat sang miễn phí

Chat trước đó chạy trên OpenRouter, đốt token các model free-tier liên tục bị rate-limit. Cloudflare Workers AI cho bạn ~10.000 neurons mỗi ngày trên plan free. Với một site cá nhân thì coi như không giới hạn.

Tích hợp gói gọn trong chín dòng cộng một wrangler binding:

import { createWorkersAI } from 'workers-ai-provider'

const workersAI = createWorkersAI({ binding: env.AI })

const result = streamText({
  model: workersAI('@cf/meta/llama-3.1-8b-instruct'),
  system,
  messages: modelMessages,
  maxOutputTokens: 512,
})
return result.toUIMessageStreamResponse()

Lỗi làm panel chat “biến mất” hóa ra không liên quan đến provider nào. Hai client:idle island riêng biệt mỗi cái tự khởi tạo store Zustand riêng tại lúc load module — click button ở island A không bao giờ lan tới panel ở island B. Cộng thêm class Tailwind bg-background tôi viết không tồn tại (không có --color-background tương ứng trong block @theme). Hai bug nấp sau nhau.

Fix là gộp về một ChatWidget island duy nhất. Hai island sụp lại còn một, và store nghiễm nhiên được dùng chung.

Lần sau làm gì khác

Thẳng thắn, đợt redesign này lẽ ra phải làm sớm hơn. Tôi đã đánh bóng copy trên một layout không xứng đáng. Bài học đáng giữ: khi một dự án thấy kẹt ở “chi tiết”, hãy kiểm tra xem cái nền có phải mới là cái đang kẹt.

Ba điều tôi sẽ làm khác:

Vụ chuyển sang Workers AI là một thắng lợi nhỏ hơn về phạm vi nhưng lớn hơn về chi phí: chat giờ chạy miễn phí vô thời hạn. Nếu khách chạm trần neurons trong ngày, request trả 429 và hôm sau lại sạch. Đủ tốt cho portfolio. Không phải kiểu quyết định tôi sẽ dùng cho sản phẩm có khách trả tiền, nhưng là kiểu tôi sẽ tiếp tục áp dụng cho mọi thứ khác.