ytianyi.com
The site you are reading. A drafting-sheet aesthetic with a computer-vision detection motif, where a small SVG instrument measures the real ink of a heading and draws a box around it. This is the process behind it.
1.0 Summary
This is a self-referential case study: the report is written in the same system it describes. The site is a personal portfolio built to read like an engineering drawing rather than a landing page. Its one conceptual move is to treat the page like a scene a detector is looking at, so headings get measured and boxed the way an object detector boxes what it finds.
It is built on Astro, ships almost no client JavaScript, and the CSS is written by hand with no framework. The interesting engineering is a single component that measures the actual rendered text of an element, not its layout box, so a label lands on the glyphs instead of near them.
2.0 The Brief
The goal was a portfolio for robotics and software roles that did not look like everyone else's. Most student sites reach for a dark theme, big gradients, and a wall of skill bars. I wanted the opposite: something that looked considered and technical, that carried a single idea all the way through, and that a recruiter could scan in thirty seconds.
Two hard constraints came out of that. Skills are never scored or ranked, because a number next to a skill reads as a weakness the moment it is not the highest number. And the writing stays plain: American spellings, short declarative sentences, no em dashes, with · and → as the only ornament.
3.0 Design Direction
The aesthetic is a drafting sheet. One-pixel rules, hard corners, a dot grid in the background, monospaced uppercase labels, and no rounded corners, gradients, or shadows anywhere. Every panel states its own metadata the way a title block on a drawing does.
3.1 The Detection Motif
The through-line is object detection. The page is framed as something being looked at, so on load a sweep passes over it and key elements lock into bounding boxes with a confidence tag, exactly like a detector's output overlaid on an image. Hovering a phrase re-boxes it and classifies it: the school name reads school 0.99, a library reads model 0.96, and so on.
This is why the tags carry decimals. They are not real confidences, they are a visual grammar borrowed from detection output, and they let the site label a fact without a caption. A face in a photo becomes tim 0.99; a skill becomes a class.
3.2 Palette & Type
The surface is warm rather than white, so the sheet reads as paper rather than an app. Cream background, a slightly lighter surface, near-black ink, and a single structural blue as the only accent. The dot grid is fixed to the viewport and masked at the top and bottom edges so it never fights the content.
Type is a system sans for reading with a monospace for every label, index number, and tag, on a fluid scale so the same page holds together from a phone to a wide display without breakpoints fighting each other.
4.0 The Detector
The detector is one component. It owns three things that all share the same per-frame placement loop: the typed-name entrance, the hover inspection boxes, and a cursor reticle. Keeping them together is deliberate; splitting them re-introduced drift between a box and the thing it was supposed to sit on.
4.1 Measuring Real Ink
The core problem is that a heading's layout box is not its ink. A line of text sits inside a taller line-height, and the last line of a wrapped paragraph usually ends well short of the right edge. Boxing the layout box draws a rectangle with visible slack on every side, which looks broken next to text that is supposed to be precisely detected.
So the measuring function walks the actual text nodes and builds the box from their client rects, not the element's. When a phrase is a single line it measures the true glyph metrics through the canvas TextMetrics interface, including the real ascent and descent, so the box hugs the letters. Baseline is measured once per font signature in an isolated probe and cached. The viewbox is derived from the document's client width rather than the window, so the scrollbar does not throw the boxes a few pixels sideways.
There is a matching escape hatch. Where an element's visual extent is not its text, like a framed photo whose extent is its border, the component emits an explicit flag so the box uses the layout box on purpose. Getting that flag wrong was a recurring bug, so the components that need it now emit it themselves rather than trusting the markup to remember.
4.2 The Entrance
On the landing page the name is typed out, flies into its slot in the hero, hands off to the real heading, and then a sweep passes down the page and the structural boxes lock in. It runs once per session, tracked in session storage, and honors reduced-motion by simply showing the finished page.
The whole thing is a fail-safe. The content is visible by default and only hidden while the animation owns the screen, with a timer that reveals everything if the detector never boots. An earlier version used an opaque overlay that could strand the page blank if anything went wrong; that was removed, and the current ordering means JavaScript off, JavaScript broken, or reduced motion all land on a readable page.
4.3 Two Box Systems
There are two box systems and they never merge. The entrance boxes a small number of big structural blocks. Hover boxes a larger number of small details inside the prose. An early version used one set for both, and the entrance became a wall of boxes, so they were split and kept separate on purpose.
5.0 Architecture
Astro was the right fit because most of the site is static content that wants to ship as HTML. Pages are .astro files, shared chrome lives in two layouts, and small pieces like a keyword, a figure, or an index row are components. Case studies like this one run through a document layout that generates its own contents rail and highlights the section you are reading.
5.1 Content as Data
Content is kept out of the markup. A single data file holds the nav, the contacts, the skills, the projects, and the roles, and the pages stay layout while the components stay dumb. That is what lets a skill know which projects used it without anything being written twice: the skill-to-project map is derived from the project list, so it cannot drift.
Assets follow the same idea. Drop a file named after a project's slug into the right folder and its preview appears, with no code to change. A missing file renders a drafted placeholder and emits no broken image.
6.0 Performance
Every image runs through Astro's asset pipeline into responsive webp across three widths. That matters more than it sounds: the hero photograph is a multi-megabyte original and ships as a fraction of that. One early mistake shipped a full-resolution derivative as the fallback source, which quietly made the landing page several times heavier than it needed to be, so every image now passes an explicit maximum width.
7.0 Build & Deploy
The site builds in CI and deploys to Cloudflare Pages on every push to the main branch. Two decisions keep that reliable. The Node version is pinned, because the toolchain rejects older runtimes. And large media is stored as ordinary git objects rather than through Git LFS, after a checkout without the LFS data once produced a build of silently broken images.
8.0 Status
Live and in active iteration. The detection motif is the part I keep pushing on: the newest work extends it so hovering a skill highlights the projects that used it, turning the same grammar into navigation rather than decoration.