[{"data":1,"prerenderedAt":270},["ShallowReactive",2],{"post-semantic-html-still-matters":3,"blog-posts":14},{"_path":4,"title":5,"description":6,"date":7,"tags":8,"readingTime":12,"body":13},"\u002Fblog\u002Fsemantic-html-still-matters\u002F","Semantic HTML Still Matters","Landmarks, headings, and native elements that make pages easier to use, style, and maintain.","2025-07-06",[9,10,11],"html","accessibility","frontend",5,"# Semantic HTML Still Matters\n\nDiv soup still ships in production because it feels faster in the moment. Semantic HTML pays that time back in accessibility, SEO, and CSS that does not fight the DOM.\n\n## Use landmarks on every page\n\n`\u003Cheader>`, `\u003Cnav>`, `\u003Cmain>`, and `\u003Cfooter>` give screen readers a map of the page. Skip links and focus management work better when `main` actually wraps the primary content—and when there is only one `main` per document.\n\nI treat landmarks as a checklist when I scaffold a layout, not as decoration after the design is done:\n\n1. One site-wide `\u003Cheader>` for branding and primary chrome\n2. `\u003Cnav>` for major navigation (and another for in-page or footer links when it helps)\n3. A single `\u003Cmain>` for the page’s primary job\n4. `\u003Cfooter>` for secondary links and meta\n5. `\u003Caside>` only for truly complementary content, not every sidebar-looking column\n\nLandmark misuse is common. Nested `\u003Cheader>` elements inside every card are fine in HTML5, but wrapping the whole app in a second `\u003Cmain>` for a modal “page” is not. Overusing `\u003Csection>` without headings also creates noise: a section should introduce a thematic grouping with a heading, not replace a `div` for styling.\n\nWhen I review a Nuxt or Vue layout, I ask whether a keyboard user could jump to main content after a few Tab presses or via a skip link. If the answer is no, the landmarks are incomplete or the focus order is wrong—semantics alone will not fix a broken tab sequence, but they make the intended structure discoverable.\n\n## Headings are a table of contents\n\nOne `\u003Ch1>` per page, then descend in order without skipping levels for styling convenience. If you need a smaller visual size, change the CSS—not the heading level.\n\nAssistive tech and many SEO tools build an outline from heading ranks. Skipping from `h1` to `h4` because “it looks right” produces a broken outline. The fix is boring and reliable:\n\n```css\n.card-title {\n  font-size: 1.125rem;\n  font-weight: 600;\n}\n```\n\n```html\n\u003Carticle>\n  \u003Ch2 class=\"card-title\">Project name\u003C\u002Fh2>\n\u003C\u002Farticle>\n```\n\nThe visual size lives in CSS; the rank stays honest. On blog indexes I keep the page `h1` as the section title (“Blog”) and use `h2` for post titles. On a post page the post title is the `h1`, and in-article sections use `h2`\u002F`h3`. That pattern stays consistent across the site and makes “where am I?” obvious.\n\nI also avoid heading-only “fake buttons.” If something is an action, it is a control. If it is a title, it is a heading. Mixing those roles is how you get clickable `h3`s with no keyboard affordance and no clear name for screen readers.\n\n## Reach for native elements first\n\nButtons for actions, links for navigation, `\u003Cbutton type=\"submit\">` inside forms. Native controls bring keyboard support and sensible defaults for free.\n\nThe mistakes I still catch in code review:\n\n- `\u003Cdiv @click>` for primary actions—no Enter\u002FSpace, no disabled semantics, no form participation\n- `\u003Ca href=\"#\">` or `href=\"javascript:void(0)\"` for in-page behavior—links should go somewhere; use a button for side effects\n- Custom select\u002Fcheckbox widgets rebuilt from scratch before checking whether a styled native control is enough\n\n```html\n\u003Carticle>\n  \u003Cheader>\n    \u003Ch2>Post title\u003C\u002Fh2>\n    \u003Ctime datetime=\"2025-07-06\">July 6, 2025\u003C\u002Ftime>\n  \u003C\u002Fheader>\n  \u003Cp>Body copy…\u003C\u002Fp>\n\u003C\u002Farticle>\n```\n\nNative elements also give you better hooks for CSS and testing. `button:disabled`, `a:focus-visible`, and `input:invalid` exist without a custom state machine. When you do need a custom widget (combobox, date picker with complex constraints), start from the [ARIA Authoring Practices](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FARIA\u002Fapg\u002F) patterns and keep the markup honest about role and state—do not fake a button with a span and hope listeners cover every input method.\n\nLists deserve a mention too. If you render a set of related items—tags, steps, navigation links—`\u003Cul>`\u002F`\u003Col>` communicate the grouping. A stack of `div`s with bullets painted in CSS looks fine visually and fails the “what is this set?” test for assistive tech.\n\n## Style and maintain from the structure you already have\n\nSemantic markup is not only for accessibility audits. It is a maintenance strategy. Selectors like `article > header` or `nav a` are clearer than `.card-header-wrapper-inner` trees that exist because everything was a `div`. When the DOM reflects meaning, refactors stay local: rename a class for look-and-feel without rewriting the document outline.\n\nA practical workflow I use on portfolio and content sites:\n\n1. Write the HTML (or template) with landmarks and headings first\n2. Add classes only where styling or behavior needs a hook\n3. Prefer element + context selectors for stable patterns (`main a`, `article img`)\n4. Reach for ARIA only when native HTML cannot express the role or state\n\nThat order prevents the common reverse path: design a pile of boxes, then sprinkle `role` and `aria-*` to apologize. ARIA is powerful; it is also easy to get wrong. Native HTML is the cheaper default.\n\nSEO is a side effect, not the primary goal, but it follows the same structure. Clear headings, a single main, and meaningful link text help crawlers and humans alike. Thin `div` trees with all meaning in data attributes force every consumer—browser, bot, or teammate—to reverse-engineer the page.\n\n## Wrap-up\n\nSemantic HTML is cheap insurance: clearer structure for assistive tech, more predictable styling hooks, and less JavaScript reimplementing what the platform already does. Start with landmarks and an honest heading outline, prefer native controls, and treat ARIA as a last resort. The page that is easier to navigate is usually the page that is easier to ship and maintain.",[15,23,33,41,48,56,63,71,78,85,91,99,106,113,120,128,134,142,151,159,165,172,178,186,194,201,207,213,220,222,228,234,241,248,255,263],{"_path":16,"title":17,"description":18,"date":19,"tags":20,"readingTime":12},"\u002Fblog\u002Fclient-side-tools-that-earn-trust\u002F","Client-Side Tools That Earn Trust","How to design browser tools that feel private by default—local processing, clear data boundaries, and UX that never asks people to guess where their files go.","2026-07-20",[21,11,22],"privacy","tools",{"_path":24,"title":25,"description":26,"date":27,"tags":28,"readingTime":32},"\u002Fblog\u002Fusb-c-cables-that-actually-matter\u002F","USB-C Cables That Actually Matter","How to buy USB-C cables by wattage and data speed so you stop guessing which cord charges slowly or fails a file transfer.","2026-07-14",[29,30,31],"gadgets","hardware","productivity",4,{"_path":34,"title":35,"description":36,"date":37,"tags":38,"readingTime":12},"\u002Fblog\u002Fcolor-contrast-people-can-read\u002F","Color Contrast People Can Actually Read","Quick checks for text and UI colors that stay legible in light mode, dark mode, and on mediocre displays.","2026-07-07",[10,39,40],"css","design",{"_path":42,"title":43,"description":44,"date":45,"tags":46,"readingTime":12},"\u002Fblog\u002Fportable-ssds-worth-carrying\u002F","Portable SSDs Worth Carrying","When a pocket SSD beats cloud sync for travel, backups, and large media—and how to pick one that stays fast and durable.","2026-06-26",[29,47,31],"storage",{"_path":49,"title":50,"description":51,"date":52,"tags":53,"readingTime":12},"\u002Fblog\u002Fwireless-earbuds-that-last-the-day\u002F","Wireless Earbuds That Last the Day","Simple charging and fit habits that stretch earbud battery life without babysitting the case every few hours.","2026-06-18",[29,54,55],"audio","habits",{"_path":57,"title":58,"description":59,"date":60,"tags":61,"readingTime":32},"\u002Fblog\u002Fwhen-a-second-monitor-helps\u002F","When a Second Monitor Actually Helps","A practical take on dual screens for coding, writing, and research—plus when a single good display is still the better desk.","2026-06-10",[29,31,62],"workspace",{"_path":64,"title":65,"description":66,"date":67,"tags":68,"readingTime":32},"\u002Fblog\u002Fshort-session-game-picks\u002F","Short-Session Games That Respect Your Evening","Genres and design patterns that fit a 30-minute window without the “one more hour” trap.","2026-06-04",[69,55,70],"gaming","indie",{"_path":72,"title":73,"description":74,"date":75,"tags":76,"readingTime":12},"\u002Fblog\u002Fphone-charging-habits-that-age-better\u002F","Phone Charging Habits That Age Better","Practical charging routines that keep a phone useful longer—without obsessing over the perfect battery percentage.","2026-05-23",[29,77,55],"mobile",{"_path":79,"title":80,"description":81,"date":82,"tags":83,"readingTime":12},"\u002Fblog\u002Ffinishing-side-projects-without-burning-out\u002F","Finishing Side Projects Without Burning Out","A practical playbook for shrinking scope, shipping thin vertical slices, and keeping personal projects fun long enough to actually launch.","2026-05-14",[31,84,55],"side-projects",{"_path":86,"title":87,"description":88,"date":89,"tags":90,"readingTime":12},"\u002Fblog\u002Faccessible-forms-people-finish\u002F","Accessible Forms That People Actually Finish","Practical accessibility checks for labels, errors, and focus so your forms work for more users.","2026-05-07",[10,9,11],{"_path":92,"title":93,"description":94,"date":95,"tags":96,"readingTime":12},"\u002Fblog\u002Fvue-composition-api-patterns\u002F","Practical Vue Composition API Patterns","Reusable patterns for composables, shared state, and cleaner component logic in Vue 3.","2026-04-27",[97,98,11],"vue","javascript",{"_path":100,"title":101,"description":102,"date":103,"tags":104,"readingTime":12},"\u002Fblog\u002Fdesigning-empty-states-that-teach\u002F","Designing Empty States That Teach the Product","Empty screens are not placeholders—they are onboarding. How to write, layout, and sequence first-run UI so people know what to do next.","2026-04-19",[105,11,40],"ux",{"_path":107,"title":108,"description":109,"date":110,"tags":111,"readingTime":12},"\u002Fblog\u002Fwhy-indie-games-keep-winning\u002F","Why Indie Games Keep Winning Attention","How small teams punch above their weight with focused design, personality, and smart scope.","2026-04-13",[69,70,112],"industry",{"_path":114,"title":115,"description":116,"date":117,"tags":118,"readingTime":12},"\u002Fblog\u002Fkeeping-component-props-simple\u002F","Keeping Component Props Simple","When to split a component, when to use slots, and how to avoid prop objects that grow forever.","2026-04-03",[97,11,119],"best-practices",{"_path":121,"title":122,"description":123,"date":124,"tags":125,"readingTime":12},"\u002Fblog\u002Fcalm-frontend-performance\u002F","A Calm Approach to Frontend Performance","Performance work that sticks: measure what users feel, fix the critical path first, and avoid optimization theater on personal and portfolio sites.","2026-03-26",[126,11,127],"performance","web",{"_path":129,"title":130,"description":131,"date":132,"tags":133,"readingTime":12},"\u002Fblog\u002Fcss-layout-without-the-hacks\u002F","CSS Layout Without the Hacks","Modern Flexbox and Grid techniques that replace brittle floats, magic numbers, and overflow tricks.","2026-03-17",[39,11,40],{"_path":135,"title":136,"description":137,"date":138,"tags":139,"readingTime":12},"\u002Fblog\u002Fcloud-gaming-what-gets-in-the-way\u002F","Cloud Gaming: What Still Gets in the Way","Latency, input feel, and library lock-in—why streaming games is impressive and still uneven.","2026-03-10",[69,140,141],"cloud-gaming","tech",{"_path":143,"title":144,"description":145,"date":146,"tags":147,"readingTime":12},"\u002Fblog\u002Fshipping-static-sites-with-nuxt\u002F","Shipping Static Sites with Nuxt","How I build and deploy a Nuxt site to Cloudflare Pages with predictable routes and content.","2026-03-02",[148,149,150],"nuxt","ssg","devops",{"_path":152,"title":153,"description":154,"date":155,"tags":156,"readingTime":12},"\u002Fblog\u002Fdebugging-faster-in-the-browser\u002F","Debugging Faster in the Browser","A short toolkit of DevTools habits—breakpoints, network filters, and DOM inspection—that cut debug time.","2026-02-24",[98,157,158],"devtools","debugging",{"_path":160,"title":161,"description":162,"date":163,"tags":164,"readingTime":12},"\u002Fblog\u002Fsmall-javascript-habits-that-scale\u002F","Small JavaScript Habits That Scale","Everyday habits—naming, early returns, and clearer async flow—that keep front-end codebases maintainable.","2026-02-18",[98,119,11],{"_path":166,"title":167,"description":168,"date":169,"tags":170,"readingTime":32},"\u002Fblog\u002Fhealthier-gaming-routine\u002F","Building a Healthier Gaming Routine","Simple habits for sessions that stay fun—breaks, goals, and knowing when to stop.","2026-02-02",[69,55,171],"wellness",{"_path":173,"title":174,"description":175,"date":176,"tags":177,"readingTime":12},"\u002Fblog\u002Fresponsive-images-without-layout-shift\u002F","Responsive Images Without Layout Shift","Width, height, srcset, and lazy loading patterns that keep pages stable while images load.","2026-01-22",[9,126,11],{"_path":179,"title":180,"description":181,"date":182,"tags":183,"readingTime":12},"\u002Fblog\u002Fgit-habits-for-small-teams\u002F","Git Habits for Solo and Small Teams","Branch naming, commit messages, and PR hygiene that keep history useful without slowing you down.","2026-01-09",[184,185,31],"git","workflow",{"_path":187,"title":188,"description":189,"date":190,"tags":191,"readingTime":12},"\u002Fblog\u002Fwhat-makes-a-game-stream-worth-watching\u002F","What Makes a Game Stream Worth Watching","Commentary, pacing, and community—why some streams click even when the gameplay is messy.","2025-12-14",[69,192,193],"streaming","content",{"_path":195,"title":196,"description":197,"date":198,"tags":199,"readingTime":12},"\u002Fblog\u002Fwhen-to-reach-for-typescript-vue\u002F","When to Reach for TypeScript on a Vue App","A pragmatic take on where TypeScript pays off in Vue—and where plain JS is still fine.","2025-11-30",[200,97,11],"typescript",{"_path":202,"title":203,"description":204,"date":205,"tags":206,"readingTime":12},"\u002Fblog\u002Fenvironment-variables-for-static-sites\u002F","Environment Variables for Static Sites","How to use build-time env vars in Nuxt and other static generators without leaking secrets or breaking CI.","2025-10-05",[148,150,11],{"_path":208,"title":209,"description":210,"date":211,"tags":212,"readingTime":12},"\u002Fblog\u002Fpicking-a-side-project-stack\u002F","Picking a Side Project Stack Without Overthinking It","How to choose tools for a personal project when the goal is shipping, not building the perfect architecture.","2025-09-18",[31,11,119],{"_path":214,"title":215,"description":216,"date":217,"tags":218,"readingTime":32},"\u002Fblog\u002Fco-op-games-for-busy-friends\u002F","Co-op Games That Work When Everyone Is Busy","Async-friendly and drop-in co-op picks for friend groups that rarely share the same free evening.","2025-08-22",[69,219,55],"co-op",{"_path":4,"title":5,"description":6,"date":7,"tags":221,"readingTime":12},[9,10,11],{"_path":223,"title":224,"description":225,"date":226,"tags":227,"readingTime":12},"\u002Fblog\u002Fgit-without-the-jargon\u002F","Learning Git Without the Jargon","A plain-language mental model for commits, branches, and merges when tutorials assume you already know Git.","2025-05-14",[184,185,31],{"_path":229,"title":230,"description":231,"date":232,"tags":233,"readingTime":12},"\u002Fblog\u002Fwhy-loading-states-matter\u002F","Why Loading States Matter More Than Animations","Skeletons, disabled buttons, and honest feedback beat flashy spinners when data takes time to arrive.","2025-03-28",[105,11,40],{"_path":235,"title":236,"description":237,"date":238,"tags":239,"readingTime":12},"\u002Fblog\u002Fnaming-things-so-future-you-survives\u002F","Naming Things So Future You Survives","Practical rules for naming variables, components, and files so a codebase stays readable months after the clever jokes fade.","2025-03-10",[98,119,240],"maintainability",{"_path":242,"title":243,"description":244,"date":245,"tags":246,"readingTime":12},"\u002Fblog\u002Fsave-systems-that-respect-time\u002F","Save Systems That Respect the Player’s Time","Checkpoints, autosaves, and manual slots are design choices. How thoughtful saving reduces rage-quits without deleting tension from the game.","2025-02-28",[69,247,105],"game-design",{"_path":249,"title":250,"description":251,"date":252,"tags":253,"readingTime":12},"\u002Fblog\u002Fmicrocopy-that-makes-interfaces-human\u002F","Microcopy That Makes Interfaces Feel Human","Button labels, error lines, and helper text are product design. How to write UI words that reduce hesitation without sounding like a chatbot.","2025-01-22",[105,254,40],"writing",{"_path":256,"title":257,"description":258,"date":259,"tags":260,"readingTime":12},"\u002Fblog\u002Freadmes-that-get-projects-running\u002F","READMEs That Get Projects Running","How to write a README that helps someone—including future you—install, run, and understand a project without opening a support chat.","2024-12-18",[261,262,55],"documentation","open-source",{"_path":264,"title":265,"description":266,"date":267,"tags":268,"readingTime":12},"\u002Fblog\u002Fprogressive-enhancement-still-pays-off\u002F","Progressive Enhancement Still Pays Off","Build the core experience in HTML first, then layer CSS and JavaScript so your site stays useful when networks, scripts, or devices misbehave.","2024-11-05",[9,11,269],"architecture",1784620505197]