[{"data":1,"prerenderedAt":2061},["ShallowReactive",2],{"navigation":3,"/projects/three-years-later-rebuilding-my-college-capstone":14,"/projects/three-years-later-rebuilding-my-college-capstone-surround":2054},[4],{"title":5,"path":6,"stem":7,"children":8,"page":13},"Blog","/blog","blog",[9],{"title":10,"path":11,"stem":12},"My Experience as a Junior Software Engineer (First Year)","/blog/my-experience-as-a-junior-software-engineer","blog/my-experience-as-a-junior-software-engineer",false,{"id":15,"title":16,"body":17,"buttonLabel":2038,"date":75,"description":2039,"extension":2040,"image":2041,"meta":2042,"navigation":452,"path":2043,"seo":2044,"stem":2045,"tags":2046,"__hash__":2053},"projects/projects/three-years-later-rebuilding-my-college-capstone.md","Three Years Later: Rebuilding My College Capstone",{"type":18,"value":19,"toc":2006},"minimark",[20,28,33,41,63,70,77,82,85,88,92,111,116,121,132,137,151,156,167,172,183,188,199,201,205,209,220,224,230,232,236,240,245,308,313,519,524,538,542,547,618,623,840,844,861,865,870,899,904,1075,1079,1093,1097,1102,1356,1361,1478,1482,1496,1498,1502,1506,1509,1514,1517,1542,1549,1553,1556,1576,1587,1591,1594,1618,1621,1650,1653,1657,1667,1670,1691,1694,1702,1709,1713,1719,1722,1746,1760,1763,1765,1769,1773,1790,1794,1817,1821,1828,1831,1845,1847,1851,1893,1895,1899,1918,1920,1924,1927,1933,1939,1945,1951,1957,1960,1962,1966,1972,1990,1993,1996,1998,2003],[21,22,23],"p",{},[24,25],"img",{"alt":26,"src":27},"Dashboard","/projects/capstone-reimplementation.png",[29,30,32],"h2",{"id":31},"the-backstory","THE BACKSTORY",[21,34,35,36,40],{},"In ",[37,38,39],"strong",{},"2023",", I built a Commuter Security Management System for my college\ncapstone. It was a complete platform:",[42,43,44,51,57],"ul",{},[45,46,47,50],"li",{},[37,48,49],{},"Mobile app"," for passengers to register and scan QR codes",[45,52,53,56],{},[37,54,55],{},"Backend API"," to process data and manage the system",[45,58,59,62],{},[37,60,61],{},"Admin dashboard"," to monitor everything",[21,64,65,66,69],{},"I built the admin panel with ",[37,67,68],{},"PHP, vanilla JavaScript, Bootstrap, and MySQL",".\nIt worked. It got me my degree. But it was 2023 code.",[21,71,72,73,76],{},"Fast forward to ",[37,74,75],{},"2026",". I've spent 2 years building enterprise systems at\nSummit Media with Vue 3, Nuxt 3, TypeScript, and modern patterns. Looking back\nat my capstone code?",[21,78,79],{},[37,80,81],{},"I could do so much better.",[21,83,84],{},"So I rebuilt the admin dashboard—not because I had to, but because I wanted to\nsee exactly how much I'd grown.",[86,87],"hr",{},[29,89,91],{"id":90},"the-challenge","THE CHALLENGE",[21,93,94,97,98,101,104,105,107,110],{},[37,95,96],{},"Constraint:"," Recreate the EXACT same features and UI",[99,100],"br",{},[37,102,103],{},"Goal:"," Use modern patterns I learned professionally",[99,106],{},[37,108,109],{},"Tech Stack:"," Nuxt 4, Vue 3, TypeScript, Pinia, Tailwind CSS (Frontend) + Express.js, JWT, MySQL (Backend)",[112,113,115],"h3",{"id":114},"features-to-recreate","Features to Recreate:",[21,117,118],{},[37,119,120],{},"Dashboard Analytics",[42,122,123,126,129],{},[45,124,125],{},"Real-time statistics for passengers, drivers, rides, and admins",[45,127,128],{},"Interactive charts and metrics",[45,130,131],{},"Live updates without page refresh",[21,133,134],{},[37,135,136],{},"User Management",[42,138,139,142,145,148],{},[45,140,141],{},"Manage passenger accounts",[45,143,144],{},"Manage driver accounts",[45,146,147],{},"Manage admin accounts",[45,149,150],{},"Role-based access control",[21,152,153],{},[37,154,155],{},"Ride Tracking",[42,157,158,161,164],{},[45,159,160],{},"View all rides in real-time",[45,162,163],{},"Monitor ride status (pending, active, completed)",[45,165,166],{},"Detailed ride information and history",[21,168,169],{},[37,170,171],{},"Authentication",[42,173,174,177,180],{},[45,175,176],{},"Secure login system",[45,178,179],{},"Protected routes with middleware",[45,181,182],{},"Session management",[21,184,185],{},[37,186,187],{},"Responsive Design",[42,189,190,193,196],{},[45,191,192],{},"Mobile-friendly interface",[45,194,195],{},"Works on all screen sizes",[45,197,198],{},"Touch-optimized interactions",[86,200],{},[29,202,204],{"id":203},"what-i-built","WHAT I BUILT",[112,206,208],{"id":207},"the-old-way-2023-capstone","The Old Way (2023 Capstone)",[210,211,216],"pre",{"className":212,"code":214,"language":215},[213],"language-text","Tech Stack:\n- PHP (backend + frontend logic)\n- Vanilla JavaScript (DOM manipulation)\n- Bootstrap (styling)\n- MySQL (database)\n\nArchitecture:\n- Monolithic PHP files\n- Mixed concerns (HTML + PHP + SQL)\n- Manual DOM updates\n- No type safety\n- ~2500 lines of mixed code\n","text",[217,218,214],"code",{"__ignoreMap":219},"",[112,221,223],{"id":222},"the-new-way-2026-rebuild","The New Way (2026 Rebuild)",[210,225,228],{"className":226,"code":227,"language":215},[213],"Frontend:\n- Nuxt 4 (framework)\n- Vue 3 (reactive UI)\n- TypeScript (type safety)\n- Pinia (state management)\n- Tailwind CSS (utility-first styling)\n- Lucide Icons (modern iconography)\n- ~1400 lines of organized, typed code\n\nBackend (Separate Repository):\n- Express.js (Node.js framework)\n- JWT authentication with bcryptjs\n- MySQL database\n- RESTful API endpoints\n- CORS support\n- Comprehensive error handling\n\nArchitecture:\n- Decoupled frontend and backend\n- Component-based design (frontend)\n- MVC pattern (backend)\n- Reactive state management\n- Full type safety\n",[217,229,227],{"__ignoreMap":219},[86,231],{},[29,233,235],{"id":234},"the-technical-deep-dive","THE TECHNICAL DEEP DIVE",[112,237,239],{"id":238},"_1-component-architecture-vs-procedural-code","1. Component Architecture vs Procedural Code",[21,241,242],{},[37,243,244],{},"Old approach (PHP):",[210,246,250],{"className":247,"code":248,"language":249,"meta":219,"style":219},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?php\n// users.php - Everything in one file\nif ($_GET['action'] == 'list') {\n    $users = mysqli_query($conn, \"SELECT * FROM users\");\n    while ($row = mysqli_fetch_assoc($users)) {\n        echo \"\u003Ctr>\u003Ctd>\" . $row['name'] . \"\u003C/td>\u003C/tr>\";\n    }\n}\n?>\n","php",[217,251,252,260,266,272,278,284,290,296,302],{"__ignoreMap":219},[253,254,257],"span",{"class":255,"line":256},"line",1,[253,258,259],{},"\u003C?php\n",[253,261,263],{"class":255,"line":262},2,[253,264,265],{},"// users.php - Everything in one file\n",[253,267,269],{"class":255,"line":268},3,[253,270,271],{},"if ($_GET['action'] == 'list') {\n",[253,273,275],{"class":255,"line":274},4,[253,276,277],{},"    $users = mysqli_query($conn, \"SELECT * FROM users\");\n",[253,279,281],{"class":255,"line":280},5,[253,282,283],{},"    while ($row = mysqli_fetch_assoc($users)) {\n",[253,285,287],{"class":255,"line":286},6,[253,288,289],{},"        echo \"\u003Ctr>\u003Ctd>\" . $row['name'] . \"\u003C/td>\u003C/tr>\";\n",[253,291,293],{"class":255,"line":292},7,[253,294,295],{},"    }\n",[253,297,299],{"class":255,"line":298},8,[253,300,301],{},"}\n",[253,303,305],{"class":255,"line":304},9,[253,306,307],{},"?>\n",[21,309,310],{},[37,311,312],{},"New approach (Vue/Nuxt):",[210,314,318],{"className":315,"code":316,"language":317,"meta":219,"style":219},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// components/DataTable.vue - Reusable component\n\u003Ctemplate>\n  \u003CTable>\n    \u003CTableRow v-for=\"user in users\" :key=\"user.id\">\n      \u003CTableCell>{{ user.name }}\u003C/TableCell>\n    \u003C/TableRow>\n  \u003C/Table>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nconst { users } = await useUsers(); // Type-safe composable\n\u003C/script>\n","typescript",[217,319,320,326,339,349,390,420,430,439,448,454,473,509],{"__ignoreMap":219},[253,321,322],{"class":255,"line":256},[253,323,325],{"class":324},"sHwdD","// components/DataTable.vue - Reusable component\n",[253,327,328,332,336],{"class":255,"line":262},[253,329,331],{"class":330},"sTEyZ","\u003C",[253,333,335],{"class":334},"sBMFI","template",[253,337,338],{"class":330},">\n",[253,340,341,344,347],{"class":255,"line":268},[253,342,343],{"class":330},"  \u003C",[253,345,346],{"class":334},"Table",[253,348,338],{"class":330},[253,350,351,355,358,361,364,367,370,374,376,379,381,383,386,388],{"class":255,"line":274},[253,352,354],{"class":353},"sMK4o","    \u003C",[253,356,357],{"class":330},"TableRow v",[253,359,360],{"class":353},"-",[253,362,363],{"class":330},"for",[253,365,366],{"class":353},"=",[253,368,369],{"class":353},"\"",[253,371,373],{"class":372},"sfazB","user in users",[253,375,369],{"class":353},[253,377,378],{"class":330}," :key",[253,380,366],{"class":353},[253,382,369],{"class":353},[253,384,385],{"class":372},"user.id",[253,387,369],{"class":353},[253,389,338],{"class":353},[253,391,392,395,398,401,404,407,410,413,416,418],{"class":255,"line":280},[253,393,394],{"class":330},"      \u003C",[253,396,397],{"class":334},"TableCell",[253,399,400],{"class":330},">",[253,402,403],{"class":353},"{{",[253,405,406],{"class":330}," user",[253,408,409],{"class":353},".",[253,411,412],{"class":330},"name",[253,414,415],{"class":353}," }}\u003C/",[253,417,397],{"class":330},[253,419,338],{"class":353},[253,421,422,425,428],{"class":255,"line":286},[253,423,424],{"class":353},"    \u003C/",[253,426,427],{"class":330},"TableRow",[253,429,338],{"class":353},[253,431,432,435,437],{"class":255,"line":292},[253,433,434],{"class":353},"  \u003C/",[253,436,346],{"class":330},[253,438,338],{"class":353},[253,440,441,444,446],{"class":255,"line":298},[253,442,443],{"class":353},"\u003C/",[253,445,335],{"class":330},[253,447,338],{"class":353},[253,449,450],{"class":255,"line":304},[253,451,453],{"emptyLinePlaceholder":452},true,"\n",[253,455,457,459,462,464,466,469,471],{"class":255,"line":456},10,[253,458,331],{"class":353},[253,460,461],{"class":330},"script setup lang",[253,463,366],{"class":353},[253,465,369],{"class":353},[253,467,468],{"class":372},"ts",[253,470,369],{"class":353},[253,472,338],{"class":353},[253,474,476,480,483,486,489,492,496,500,503,506],{"class":255,"line":475},11,[253,477,479],{"class":478},"spNyl","const",[253,481,482],{"class":353}," {",[253,484,485],{"class":330}," users ",[253,487,488],{"class":353},"}",[253,490,491],{"class":353}," =",[253,493,495],{"class":494},"s7zQu"," await",[253,497,499],{"class":498},"s2Zo4"," useUsers",[253,501,502],{"class":330},"()",[253,504,505],{"class":353},";",[253,507,508],{"class":324}," // Type-safe composable\n",[253,510,512,514,517],{"class":255,"line":511},12,[253,513,443],{"class":353},[253,515,516],{"class":330},"script",[253,518,338],{"class":353},[21,520,521],{},[37,522,523],{},"Why this is better:",[42,525,526,529,532,535],{},[45,527,528],{},"Reusable across the app",[45,530,531],{},"Type-safe (TypeScript catches errors)",[45,533,534],{},"Reactive (updates automatically when data changes)",[45,536,537],{},"Testable (can test components in isolation)",[112,539,541],{"id":540},"_2-state-management-global-variables-pinia","2. State Management: Global Variables → Pinia",[21,543,544],{},[37,545,546],{},"Old approach:",[210,548,552],{"className":549,"code":550,"language":551,"meta":219,"style":219},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// Global variables scattered everywhere\nvar currentUser = null;\nvar rideData = [];\n\nfunction loadRides() {\n  $.ajax({\n    url: \"api/rides.php\",\n    success: function (data) {\n      rideData = data;\n      updateUI();\n    },\n  });\n}\n","javascript",[217,553,554,559,564,569,573,578,583,588,593,598,603,608,613],{"__ignoreMap":219},[253,555,556],{"class":255,"line":256},[253,557,558],{},"// Global variables scattered everywhere\n",[253,560,561],{"class":255,"line":262},[253,562,563],{},"var currentUser = null;\n",[253,565,566],{"class":255,"line":268},[253,567,568],{},"var rideData = [];\n",[253,570,571],{"class":255,"line":274},[253,572,453],{"emptyLinePlaceholder":452},[253,574,575],{"class":255,"line":280},[253,576,577],{},"function loadRides() {\n",[253,579,580],{"class":255,"line":286},[253,581,582],{},"  $.ajax({\n",[253,584,585],{"class":255,"line":292},[253,586,587],{},"    url: \"api/rides.php\",\n",[253,589,590],{"class":255,"line":298},[253,591,592],{},"    success: function (data) {\n",[253,594,595],{"class":255,"line":304},[253,596,597],{},"      rideData = data;\n",[253,599,600],{"class":255,"line":456},[253,601,602],{},"      updateUI();\n",[253,604,605],{"class":255,"line":475},[253,606,607],{},"    },\n",[253,609,610],{"class":255,"line":511},[253,611,612],{},"  });\n",[253,614,616],{"class":255,"line":615},13,[253,617,301],{},[21,619,620],{},[37,621,622],{},"New approach:",[210,624,626],{"className":315,"code":625,"language":317,"meta":219,"style":219},"// stores/rides.ts - Centralized, typed state\nexport const useRidesStore = defineStore(\"rides\", () => {\n  const rides = ref\u003CRide[]>([]);\n  const loading = ref(false);\n\n  async function fetchRides() {\n    loading.value = true;\n    rides.value = await $fetch(\"/api/rides\");\n    loading.value = false;\n  }\n\n  return { rides, loading, fetchRides };\n});\n",[217,627,628,633,671,701,723,727,742,759,788,803,808,812,832],{"__ignoreMap":219},[253,629,630],{"class":255,"line":256},[253,631,632],{"class":324},"// stores/rides.ts - Centralized, typed state\n",[253,634,635,638,641,644,646,649,652,654,657,659,662,665,668],{"class":255,"line":262},[253,636,637],{"class":494},"export",[253,639,640],{"class":478}," const",[253,642,643],{"class":330}," useRidesStore ",[253,645,366],{"class":353},[253,647,648],{"class":498}," defineStore",[253,650,651],{"class":330},"(",[253,653,369],{"class":353},[253,655,656],{"class":372},"rides",[253,658,369],{"class":353},[253,660,661],{"class":353},",",[253,663,664],{"class":353}," ()",[253,666,667],{"class":478}," =>",[253,669,670],{"class":353}," {\n",[253,672,673,676,679,681,684,686,689,693,695,698],{"class":255,"line":268},[253,674,675],{"class":478},"  const",[253,677,678],{"class":330}," rides",[253,680,491],{"class":353},[253,682,683],{"class":498}," ref",[253,685,331],{"class":353},[253,687,688],{"class":334},"Ride",[253,690,692],{"class":691},"swJcz","[]",[253,694,400],{"class":353},[253,696,697],{"class":691},"([])",[253,699,700],{"class":353},";\n",[253,702,703,705,708,710,712,714,718,721],{"class":255,"line":274},[253,704,675],{"class":478},[253,706,707],{"class":330}," loading",[253,709,491],{"class":353},[253,711,683],{"class":498},[253,713,651],{"class":691},[253,715,717],{"class":716},"sfNiH","false",[253,719,720],{"class":691},")",[253,722,700],{"class":353},[253,724,725],{"class":255,"line":280},[253,726,453],{"emptyLinePlaceholder":452},[253,728,729,732,735,738,740],{"class":255,"line":286},[253,730,731],{"class":478},"  async",[253,733,734],{"class":478}," function",[253,736,737],{"class":498}," fetchRides",[253,739,502],{"class":353},[253,741,670],{"class":353},[253,743,744,747,749,752,754,757],{"class":255,"line":292},[253,745,746],{"class":330},"    loading",[253,748,409],{"class":353},[253,750,751],{"class":330},"value",[253,753,491],{"class":353},[253,755,756],{"class":716}," true",[253,758,700],{"class":353},[253,760,761,764,766,768,770,772,775,777,779,782,784,786],{"class":255,"line":298},[253,762,763],{"class":330},"    rides",[253,765,409],{"class":353},[253,767,751],{"class":330},[253,769,491],{"class":353},[253,771,495],{"class":494},[253,773,774],{"class":498}," $fetch",[253,776,651],{"class":691},[253,778,369],{"class":353},[253,780,781],{"class":372},"/api/rides",[253,783,369],{"class":353},[253,785,720],{"class":691},[253,787,700],{"class":353},[253,789,790,792,794,796,798,801],{"class":255,"line":304},[253,791,746],{"class":330},[253,793,409],{"class":353},[253,795,751],{"class":330},[253,797,491],{"class":353},[253,799,800],{"class":716}," false",[253,802,700],{"class":353},[253,804,805],{"class":255,"line":456},[253,806,807],{"class":353},"  }\n",[253,809,810],{"class":255,"line":475},[253,811,453],{"emptyLinePlaceholder":452},[253,813,814,817,819,821,823,825,827,829],{"class":255,"line":511},[253,815,816],{"class":494},"  return",[253,818,482],{"class":353},[253,820,678],{"class":330},[253,822,661],{"class":353},[253,824,707],{"class":330},[253,826,661],{"class":353},[253,828,737],{"class":330},[253,830,831],{"class":353}," };\n",[253,833,834,836,838],{"class":255,"line":615},[253,835,488],{"class":353},[253,837,720],{"class":330},[253,839,700],{"class":353},[21,841,842],{},[37,843,523],{},[42,845,846,849,855,858],{},[45,847,848],{},"Single source of truth",[45,850,851,852,854],{},"Type-safe (Ride",[253,853],{}," enforces structure)",[45,856,857],{},"Reactive (components auto-update)",[45,859,860],{},"Predictable state flow",[112,862,864],{"id":863},"_3-type-safety-runtime-errors-compile-time-errors","3. Type Safety: Runtime Errors → Compile-Time Errors",[21,866,867],{},[37,868,869],{},"Old approach (vanilla JS):",[210,871,873],{"className":549,"code":872,"language":551,"meta":219,"style":219},"function displayRide(ride) {\n  // What properties does ride have? Who knows!\n  document.getElementById(\"status\").innerHTML = ride.staus; // Typo!\n  // This error only shows up when you run the code\n}\n",[217,874,875,880,885,890,895],{"__ignoreMap":219},[253,876,877],{"class":255,"line":256},[253,878,879],{},"function displayRide(ride) {\n",[253,881,882],{"class":255,"line":262},[253,883,884],{},"  // What properties does ride have? Who knows!\n",[253,886,887],{"class":255,"line":268},[253,888,889],{},"  document.getElementById(\"status\").innerHTML = ride.staus; // Typo!\n",[253,891,892],{"class":255,"line":274},[253,893,894],{},"  // This error only shows up when you run the code\n",[253,896,897],{"class":255,"line":280},[253,898,301],{},[21,900,901],{},[37,902,903],{},"New approach (TypeScript):",[210,905,907],{"className":315,"code":906,"language":317,"meta":219,"style":219},"interface Ride {\n  id: string;\n  status: \"pending\" | \"active\" | \"completed\";\n  passenger: User;\n  driver: User;\n}\n\nfunction displayRide(ride: Ride) {\n  console.log(ride.staus); // ❌ TypeScript error: Property 'staus' does not exist\n  console.log(ride.status); // ✅ Correct\n}\n",[217,908,909,919,932,968,980,991,995,999,1021,1047,1071],{"__ignoreMap":219},[253,910,911,914,917],{"class":255,"line":256},[253,912,913],{"class":478},"interface",[253,915,916],{"class":334}," Ride",[253,918,670],{"class":353},[253,920,921,924,927,930],{"class":255,"line":262},[253,922,923],{"class":691},"  id",[253,925,926],{"class":353},":",[253,928,929],{"class":334}," string",[253,931,700],{"class":353},[253,933,934,937,939,942,945,947,950,952,955,957,959,961,964,966],{"class":255,"line":268},[253,935,936],{"class":691},"  status",[253,938,926],{"class":353},[253,940,941],{"class":353}," \"",[253,943,944],{"class":372},"pending",[253,946,369],{"class":353},[253,948,949],{"class":353}," |",[253,951,941],{"class":353},[253,953,954],{"class":372},"active",[253,956,369],{"class":353},[253,958,949],{"class":353},[253,960,941],{"class":353},[253,962,963],{"class":372},"completed",[253,965,369],{"class":353},[253,967,700],{"class":353},[253,969,970,973,975,978],{"class":255,"line":274},[253,971,972],{"class":691},"  passenger",[253,974,926],{"class":353},[253,976,977],{"class":334}," User",[253,979,700],{"class":353},[253,981,982,985,987,989],{"class":255,"line":280},[253,983,984],{"class":691},"  driver",[253,986,926],{"class":353},[253,988,977],{"class":334},[253,990,700],{"class":353},[253,992,993],{"class":255,"line":286},[253,994,301],{"class":353},[253,996,997],{"class":255,"line":292},[253,998,453],{"emptyLinePlaceholder":452},[253,1000,1001,1004,1007,1009,1013,1015,1017,1019],{"class":255,"line":298},[253,1002,1003],{"class":478},"function",[253,1005,1006],{"class":498}," displayRide",[253,1008,651],{"class":353},[253,1010,1012],{"class":1011},"sHdIc","ride",[253,1014,926],{"class":353},[253,1016,916],{"class":334},[253,1018,720],{"class":353},[253,1020,670],{"class":353},[253,1022,1023,1026,1028,1031,1033,1035,1037,1040,1042,1044],{"class":255,"line":304},[253,1024,1025],{"class":330},"  console",[253,1027,409],{"class":353},[253,1029,1030],{"class":498},"log",[253,1032,651],{"class":691},[253,1034,1012],{"class":330},[253,1036,409],{"class":353},[253,1038,1039],{"class":330},"staus",[253,1041,720],{"class":691},[253,1043,505],{"class":353},[253,1045,1046],{"class":324}," // ❌ TypeScript error: Property 'staus' does not exist\n",[253,1048,1049,1051,1053,1055,1057,1059,1061,1064,1066,1068],{"class":255,"line":456},[253,1050,1025],{"class":330},[253,1052,409],{"class":353},[253,1054,1030],{"class":498},[253,1056,651],{"class":691},[253,1058,1012],{"class":330},[253,1060,409],{"class":353},[253,1062,1063],{"class":330},"status",[253,1065,720],{"class":691},[253,1067,505],{"class":353},[253,1069,1070],{"class":324}," // ✅ Correct\n",[253,1072,1073],{"class":255,"line":475},[253,1074,301],{"class":353},[21,1076,1077],{},[37,1078,523],{},[42,1080,1081,1084,1087,1090],{},[45,1082,1083],{},"Catches typos before running code",[45,1085,1086],{},"Self-documenting (you know what properties exist)",[45,1088,1089],{},"Better IDE autocomplete",[45,1091,1092],{},"Refactoring is safer",[112,1094,1096],{"id":1095},"_4-styling-custom-css-tailwind-utility-classes","4. Styling: Custom CSS → Tailwind Utility Classes",[21,1098,1099],{},[37,1100,1101],{},"Old approach (Bootstrap + custom CSS):",[210,1103,1107],{"className":1104,"code":1105,"language":1106,"meta":219,"style":219},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cdiv class=\"card user-card\">\n  \u003Cdiv class=\"card-header bg-primary\">\n    \u003Ch3 class=\"card-title\">Users\u003C/h3>\n  \u003C/div>\n  \u003Cdiv class=\"card-body\">\n    \u003C!-- content -->\n  \u003C/div>\n\u003C/div>\n\n\u003Cstyle>\n  .user-card {\n    margin: 20px;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n  }\n  .card-title {\n    font-weight: 600;\n    color: white;\n  }\n\u003C/style>\n","html",[217,1108,1109,1130,1149,1177,1185,1204,1209,1217,1225,1229,1238,1248,1262,1302,1307,1316,1329,1342,1347],{"__ignoreMap":219},[253,1110,1111,1113,1116,1119,1121,1123,1126,1128],{"class":255,"line":256},[253,1112,331],{"class":353},[253,1114,1115],{"class":691},"div",[253,1117,1118],{"class":478}," class",[253,1120,366],{"class":353},[253,1122,369],{"class":353},[253,1124,1125],{"class":372},"card user-card",[253,1127,369],{"class":353},[253,1129,338],{"class":353},[253,1131,1132,1134,1136,1138,1140,1142,1145,1147],{"class":255,"line":262},[253,1133,343],{"class":353},[253,1135,1115],{"class":691},[253,1137,1118],{"class":478},[253,1139,366],{"class":353},[253,1141,369],{"class":353},[253,1143,1144],{"class":372},"card-header bg-primary",[253,1146,369],{"class":353},[253,1148,338],{"class":353},[253,1150,1151,1153,1155,1157,1159,1161,1164,1166,1168,1171,1173,1175],{"class":255,"line":268},[253,1152,354],{"class":353},[253,1154,112],{"class":691},[253,1156,1118],{"class":478},[253,1158,366],{"class":353},[253,1160,369],{"class":353},[253,1162,1163],{"class":372},"card-title",[253,1165,369],{"class":353},[253,1167,400],{"class":353},[253,1169,1170],{"class":330},"Users",[253,1172,443],{"class":353},[253,1174,112],{"class":691},[253,1176,338],{"class":353},[253,1178,1179,1181,1183],{"class":255,"line":274},[253,1180,434],{"class":353},[253,1182,1115],{"class":691},[253,1184,338],{"class":353},[253,1186,1187,1189,1191,1193,1195,1197,1200,1202],{"class":255,"line":280},[253,1188,343],{"class":353},[253,1190,1115],{"class":691},[253,1192,1118],{"class":478},[253,1194,366],{"class":353},[253,1196,369],{"class":353},[253,1198,1199],{"class":372},"card-body",[253,1201,369],{"class":353},[253,1203,338],{"class":353},[253,1205,1206],{"class":255,"line":286},[253,1207,1208],{"class":324},"    \u003C!-- content -->\n",[253,1210,1211,1213,1215],{"class":255,"line":292},[253,1212,434],{"class":353},[253,1214,1115],{"class":691},[253,1216,338],{"class":353},[253,1218,1219,1221,1223],{"class":255,"line":298},[253,1220,443],{"class":353},[253,1222,1115],{"class":691},[253,1224,338],{"class":353},[253,1226,1227],{"class":255,"line":304},[253,1228,453],{"emptyLinePlaceholder":452},[253,1230,1231,1233,1236],{"class":255,"line":456},[253,1232,331],{"class":353},[253,1234,1235],{"class":691},"style",[253,1237,338],{"class":353},[253,1239,1240,1243,1246],{"class":255,"line":475},[253,1241,1242],{"class":353},"  .",[253,1244,1245],{"class":334},"user-card",[253,1247,670],{"class":353},[253,1249,1250,1254,1256,1260],{"class":255,"line":511},[253,1251,1253],{"class":1252},"sqsOY","    margin",[253,1255,926],{"class":353},[253,1257,1259],{"class":1258},"sbssI"," 20px",[253,1261,700],{"class":353},[253,1263,1264,1267,1269,1272,1275,1278,1281,1283,1286,1288,1290,1292,1294,1296,1299],{"class":255,"line":615},[253,1265,1266],{"class":1252},"    box-shadow",[253,1268,926],{"class":353},[253,1270,1271],{"class":1258}," 0",[253,1273,1274],{"class":1258}," 2px",[253,1276,1277],{"class":1258}," 4px",[253,1279,1280],{"class":498}," rgba",[253,1282,651],{"class":353},[253,1284,1285],{"class":1258},"0",[253,1287,661],{"class":353},[253,1289,1271],{"class":1258},[253,1291,661],{"class":353},[253,1293,1271],{"class":1258},[253,1295,661],{"class":353},[253,1297,1298],{"class":1258}," 0.1",[253,1300,1301],{"class":353},");\n",[253,1303,1305],{"class":255,"line":1304},14,[253,1306,807],{"class":353},[253,1308,1310,1312,1314],{"class":255,"line":1309},15,[253,1311,1242],{"class":353},[253,1313,1163],{"class":334},[253,1315,670],{"class":353},[253,1317,1319,1322,1324,1327],{"class":255,"line":1318},16,[253,1320,1321],{"class":1252},"    font-weight",[253,1323,926],{"class":353},[253,1325,1326],{"class":1258}," 600",[253,1328,700],{"class":353},[253,1330,1332,1335,1337,1340],{"class":255,"line":1331},17,[253,1333,1334],{"class":1252},"    color",[253,1336,926],{"class":353},[253,1338,1339],{"class":330}," white",[253,1341,700],{"class":353},[253,1343,1345],{"class":255,"line":1344},18,[253,1346,807],{"class":353},[253,1348,1350,1352,1354],{"class":255,"line":1349},19,[253,1351,443],{"class":353},[253,1353,1235],{"class":691},[253,1355,338],{"class":353},[21,1357,1358],{},[37,1359,1360],{},"New approach (Tailwind CSS):",[210,1362,1364],{"className":1104,"code":1363,"language":1106,"meta":219,"style":219},"\u003Cdiv class=\"m-5 rounded-lg shadow-sm bg-white\">\n  \u003Cdiv class=\"bg-blue-600 p-4 rounded-t-lg\">\n    \u003Ch3 class=\"font-semibold text-white\">Users\u003C/h3>\n  \u003C/div>\n  \u003Cdiv class=\"p-4\">\n    \u003C!-- content -->\n  \u003C/div>\n\u003C/div>\n",[217,1365,1366,1385,1404,1431,1439,1458,1462,1470],{"__ignoreMap":219},[253,1367,1368,1370,1372,1374,1376,1378,1381,1383],{"class":255,"line":256},[253,1369,331],{"class":353},[253,1371,1115],{"class":691},[253,1373,1118],{"class":478},[253,1375,366],{"class":353},[253,1377,369],{"class":353},[253,1379,1380],{"class":372},"m-5 rounded-lg shadow-sm bg-white",[253,1382,369],{"class":353},[253,1384,338],{"class":353},[253,1386,1387,1389,1391,1393,1395,1397,1400,1402],{"class":255,"line":262},[253,1388,343],{"class":353},[253,1390,1115],{"class":691},[253,1392,1118],{"class":478},[253,1394,366],{"class":353},[253,1396,369],{"class":353},[253,1398,1399],{"class":372},"bg-blue-600 p-4 rounded-t-lg",[253,1401,369],{"class":353},[253,1403,338],{"class":353},[253,1405,1406,1408,1410,1412,1414,1416,1419,1421,1423,1425,1427,1429],{"class":255,"line":268},[253,1407,354],{"class":353},[253,1409,112],{"class":691},[253,1411,1118],{"class":478},[253,1413,366],{"class":353},[253,1415,369],{"class":353},[253,1417,1418],{"class":372},"font-semibold text-white",[253,1420,369],{"class":353},[253,1422,400],{"class":353},[253,1424,1170],{"class":330},[253,1426,443],{"class":353},[253,1428,112],{"class":691},[253,1430,338],{"class":353},[253,1432,1433,1435,1437],{"class":255,"line":274},[253,1434,434],{"class":353},[253,1436,1115],{"class":691},[253,1438,338],{"class":353},[253,1440,1441,1443,1445,1447,1449,1451,1454,1456],{"class":255,"line":280},[253,1442,343],{"class":353},[253,1444,1115],{"class":691},[253,1446,1118],{"class":478},[253,1448,366],{"class":353},[253,1450,369],{"class":353},[253,1452,1453],{"class":372},"p-4",[253,1455,369],{"class":353},[253,1457,338],{"class":353},[253,1459,1460],{"class":255,"line":286},[253,1461,1208],{"class":324},[253,1463,1464,1466,1468],{"class":255,"line":292},[253,1465,434],{"class":353},[253,1467,1115],{"class":691},[253,1469,338],{"class":353},[253,1471,1472,1474,1476],{"class":255,"line":298},[253,1473,443],{"class":353},[253,1475,1115],{"class":691},[253,1477,338],{"class":353},[21,1479,1480],{},[37,1481,523],{},[42,1483,1484,1487,1490,1493],{},[45,1485,1486],{},"No custom CSS files to maintain",[45,1488,1489],{},"Consistent spacing/colors (design system)",[45,1491,1492],{},"Faster to write (utility classes)",[45,1494,1495],{},"Smaller bundle size (unused classes purged)",[86,1497],{},[29,1499,1501],{"id":1500},"what-i-learned","WHAT I LEARNED",[112,1503,1505],{"id":1504},"_1-the-power-of-typescript","1. The Power of TypeScript",[21,1507,1508],{},"I didn't use TypeScript in my capstone. Now I can't imagine building without it.",[21,1510,1511],{},[37,1512,1513],{},"Real example from this rebuild:",[21,1515,1516],{},"I was building the user management table. In vanilla JS, I kept making mistakes:",[42,1518,1519,1529,1539],{},[45,1520,1521,1522,1525,1526],{},"Accessing ",[217,1523,1524],{},"user.fullName"," when it was actually ",[217,1527,1528],{},"user.full_name",[45,1530,1531,1532,1535,1536],{},"Trying to display ",[217,1533,1534],{},"ride.driver"," when it was sometimes ",[217,1537,1538],{},"null",[45,1540,1541],{},"Not knowing what properties existed without checking the API response",[21,1543,1544,1545,1548],{},"TypeScript caught all of these ",[37,1546,1547],{},"before I even ran the code",". That's powerful.",[112,1550,1552],{"id":1551},"_2-component-based-thinking","2. Component-Based Thinking",[21,1554,1555],{},"In my original capstone, every page was a separate PHP file with duplicated code:",[42,1557,1558,1564,1570],{},[45,1559,1560,1563],{},[217,1561,1562],{},"users.php"," had a table",[45,1565,1566,1569],{},[217,1567,1568],{},"drivers.php"," had the same table with different data",[45,1571,1572,1575],{},[217,1573,1574],{},"rides.php"," had the same table again",[21,1577,1578,1579,1582,1583,1586],{},"In Vue, I built ",[37,1580,1581],{},"one"," ",[217,1584,1585],{},"\u003CDataTable>"," component and reused it everywhere with\ndifferent data. Change one component, update all pages. That's the power of\ncomponents.",[112,1588,1590],{"id":1589},"_3-state-management-at-scale","3. State Management at Scale",[21,1592,1593],{},"In vanilla JS, I had data scattered everywhere:",[210,1595,1597],{"className":549,"code":1596,"language":551,"meta":219,"style":219},"var currentUser = null;\nvar allRides = [];\nvar filteredRides = [];\nvar selectedRide = null;\n",[217,1598,1599,1603,1608,1613],{"__ignoreMap":219},[253,1600,1601],{"class":255,"line":256},[253,1602,563],{},[253,1604,1605],{"class":255,"line":262},[253,1606,1607],{},"var allRides = [];\n",[253,1609,1610],{"class":255,"line":268},[253,1611,1612],{},"var filteredRides = [];\n",[253,1614,1615],{"class":255,"line":274},[253,1616,1617],{},"var selectedRide = null;\n",[21,1619,1620],{},"With Pinia, everything is organized:",[210,1622,1624],{"className":315,"code":1623,"language":317,"meta":219,"style":219},"useAuthStore(); // currentUser\nuseRidesStore(); // allRides, filteredRides, selectedRide\n",[217,1625,1626,1638],{"__ignoreMap":219},[253,1627,1628,1631,1633,1635],{"class":255,"line":256},[253,1629,1630],{"class":498},"useAuthStore",[253,1632,502],{"class":330},[253,1634,505],{"class":353},[253,1636,1637],{"class":324}," // currentUser\n",[253,1639,1640,1643,1645,1647],{"class":255,"line":262},[253,1641,1642],{"class":498},"useRidesStore",[253,1644,502],{"class":330},[253,1646,505],{"class":353},[253,1648,1649],{"class":324}," // allRides, filteredRides, selectedRide\n",[21,1651,1652],{},"One source of truth. Predictable updates. Way easier to debug.",[112,1654,1656],{"id":1655},"_4-modern-css-is-fast","4. Modern CSS is Fast",[21,1658,1659,1660,1663,1664,409],{},"Tailwind CSS let me recreate the ",[37,1661,1662],{},"exact same UI"," in about ",[37,1665,1666],{},"1/3 the time",[21,1668,1669],{},"Instead of:",[1671,1672,1673,1676,1679,1682,1685,1688],"ol",{},[45,1674,1675],{},"Write HTML",[45,1677,1678],{},"Add class names",[45,1680,1681],{},"Switch to CSS file",[45,1683,1684],{},"Write styles",[45,1686,1687],{},"Go back to HTML",[45,1689,1690],{},"Adjust class names",[21,1692,1693],{},"I just:",[1671,1695,1696,1699],{},[45,1697,1698],{},"Write HTML with utility classes",[45,1700,1701],{},"Done",[21,1703,1704,1705,1708],{},"The final bundle is actually ",[37,1706,1707],{},"smaller"," because Tailwind purges unused classes.",[112,1710,1712],{"id":1711},"_5-the-value-of-rebuilding","5. The Value of Rebuilding",[21,1714,1715,1716,409],{},"This wasn't just a \"portfolio project.\" It was a ",[37,1717,1718],{},"learning checkpoint",[21,1720,1721],{},"By rebuilding something I knew intimately, I could focus on:",[42,1723,1724,1734,1740],{},[45,1725,1726,1729,1730,1733],{},[37,1727,1728],{},"How"," to solve problems, not ",[37,1731,1732],{},"what"," to build",[45,1735,1736,1739],{},[37,1737,1738],{},"Why"," modern patterns are better, not just that they exist",[45,1741,1742,1745],{},[37,1743,1744],{},"Where"," I've grown as a developer",[21,1747,1748,1749,1752,1753,1755,1756,1759],{},"The original took me ",[37,1750,1751],{},"2 months"," in 2023.",[99,1754],{},"\nThis rebuild took me ",[37,1757,1758],{},"2 weeks"," in 2026.",[21,1761,1762],{},"That's growth.",[86,1764],{},[29,1766,1768],{"id":1767},"the-results","THE RESULTS",[112,1770,1772],{"id":1771},"before-2023-phpvanilla-js","Before (2023 - PHP/Vanilla JS)",[42,1774,1775,1778,1781,1784,1787],{},[45,1776,1777],{},"~2500 lines of mixed HTML/PHP/JavaScript",[45,1779,1780],{},"No type safety",[45,1782,1783],{},"Manual DOM manipulation",[45,1785,1786],{},"Duplicated code across pages",[45,1788,1789],{},"Hard to maintain and extend",[112,1791,1793],{"id":1792},"after-2026-nuxt-4typescript","After (2026 - Nuxt 4/TypeScript)",[42,1795,1796,1799,1802,1805,1808,1811,1814],{},[45,1797,1798],{},"~1400 lines of organized, typed code",[45,1800,1801],{},"Full TypeScript type safety",[45,1803,1804],{},"Component-based architecture",[45,1806,1807],{},"Reactive state management",[45,1809,1810],{},"Pinia for predictable data flow",[45,1812,1813],{},"Tailwind for consistent styling",[45,1815,1816],{},"Easy to maintain and extend",[112,1818,1820],{"id":1819},"what-this-proves","What This Proves",[21,1822,1823,1824,1827],{},"Not just that I can learn new tech, but that I understand ",[37,1825,1826],{},"why"," modern\npatterns exist.",[21,1829,1830],{},"I didn't just follow a tutorial. I rebuilt something I knew deeply and saw\nthe improvements firsthand:",[42,1832,1833,1836,1839,1842],{},[45,1834,1835],{},"Faster development",[45,1837,1838],{},"Fewer bugs",[45,1840,1841],{},"Easier maintenance",[45,1843,1844],{},"Better developer experience",[86,1846],{},[29,1848,1850],{"id":1849},"tech-stack","TECH STACK",[21,1852,1853,1856,1857,1859,1862,1863,1865,1868,1869,1871,1874,1875,1877,1880,1881,1883,1886,1887,1889,1892],{},[37,1854,1855],{},"Frontend Framework:"," Nuxt 4",[99,1858],{},[37,1860,1861],{},"UI Framework:"," Vue 3 (Composition API)",[99,1864],{},[37,1866,1867],{},"Language:"," TypeScript",[99,1870],{},[37,1872,1873],{},"State Management:"," Pinia",[99,1876],{},[37,1878,1879],{},"Styling:"," Tailwind CSS",[99,1882],{},[37,1884,1885],{},"Icons:"," Lucide Vue Next",[99,1888],{},[37,1890,1891],{},"API Client:"," Custom composables with error handling",[86,1894],{},[29,1896,1898],{"id":1897},"try-it-yourself","TRY IT YOURSELF",[21,1900,1901,1910,1911],{},[37,1902,1903],{},[1904,1905,1909],"a",{"href":1906,"rel":1907},"https://csa-management-system.vercel.app/",[1908],"nofollow","View Live Demo","   ",[37,1912,1913],{},[1904,1914,1917],{"href":1915,"rel":1916},"https://github.com/aarunzetta/csa-capstoneVersion",[1908],"View Source",[86,1919],{},[29,1921,1923],{"id":1922},"what-id-do-differently-next-time","WHAT I'D DO DIFFERENTLY NEXT TIME",[21,1925,1926],{},"If I rebuilt this again today:",[21,1928,1929,1932],{},[37,1930,1931],{},"Add Real-Time Updates","\nUse WebSockets instead of polling for truly real-time ride tracking",[21,1934,1935,1938],{},[37,1936,1937],{},"Implement E2E Testing","\nAdd Playwright tests to ensure features don't break",[21,1940,1941,1944],{},[37,1942,1943],{},"Add Data Visualization","\nMore interactive charts using D3.js or Chart.js",[21,1946,1947,1950],{},[37,1948,1949],{},"Optimize Bundle Size","\nImplement lazy loading for routes and components",[21,1952,1953,1956],{},[37,1954,1955],{},"Add Offline Support","\nService workers for offline dashboard access",[21,1958,1959],{},"But honestly? For a personal learning project, I'm proud of where it is.",[86,1961],{},[29,1963,1965],{"id":1964},"the-bigger-picture","THE BIGGER PICTURE",[21,1967,1968,1969,926],{},"This project represents ",[37,1970,1971],{},"3 years of growth",[21,1973,1974,1977,1978,1980,1983,1984,1986,1989],{},[37,1975,1976],{},"2023:"," Built my capstone with the tools I knew",[99,1979],{},[37,1981,1982],{},"2024:"," Learned modern frameworks professionally",[99,1985],{},[37,1987,1988],{},"2026:"," Rebuilt my capstone with everything I've learned",[21,1991,1992],{},"The cycle continues. What will I know in 2029 that makes this rebuild look\noutdated?",[21,1994,1995],{},"That's the exciting part—there's always more to learn.",[86,1997],{},[21,1999,2000],{},[253,2001,2002],{},"Want to discuss modern refactoring or TypeScript patterns? Let's connect →",[1235,2004,2005],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":219,"searchDepth":262,"depth":262,"links":2007},[2008,2009,2012,2016,2022,2029,2034,2035,2036,2037],{"id":31,"depth":262,"text":32},{"id":90,"depth":262,"text":91,"children":2010},[2011],{"id":114,"depth":268,"text":115},{"id":203,"depth":262,"text":204,"children":2013},[2014,2015],{"id":207,"depth":268,"text":208},{"id":222,"depth":268,"text":223},{"id":234,"depth":262,"text":235,"children":2017},[2018,2019,2020,2021],{"id":238,"depth":268,"text":239},{"id":540,"depth":268,"text":541},{"id":863,"depth":268,"text":864},{"id":1095,"depth":268,"text":1096},{"id":1500,"depth":262,"text":1501,"children":2023},[2024,2025,2026,2027,2028],{"id":1504,"depth":268,"text":1505},{"id":1551,"depth":268,"text":1552},{"id":1589,"depth":268,"text":1590},{"id":1655,"depth":268,"text":1656},{"id":1711,"depth":268,"text":1712},{"id":1767,"depth":262,"text":1768,"children":2030},[2031,2032,2033],{"id":1771,"depth":268,"text":1772},{"id":1792,"depth":268,"text":1793},{"id":1819,"depth":268,"text":1820},{"id":1849,"depth":262,"text":1850},{"id":1897,"depth":262,"text":1898},{"id":1922,"depth":262,"text":1923},{"id":1964,"depth":262,"text":1965},"View Case Study","From PHP/vanilla JS to Nuxt 4/TypeScript—what I learned by recreating my own work","md","/projects/commuter-security-app.png",{},"/projects/three-years-later-rebuilding-my-college-capstone",{"title":16,"description":2039},"projects/three-years-later-rebuilding-my-college-capstone",[2047,2048,2049,2050,2051,2052],"Nuxt.js","TypeScript","Pinia","Tailwind CSS","Express.js","MySQL","koKSodhS7uw_TxzKMmEtKYZXY1r9FdBBxrU7C_ROTgQ",[2055,2060],{"title":2056,"path":2057,"stem":2058,"description":2059,"children":-1},"Splitlog — Personal Run Management Platform","/projects/splitlog","projects/splitlog","I was tired of copy-pasting my Strava stats into Notion after every run. So I built my own platform to automate it. First full-stack project. First time with Next.js, Supabase, and real API integrations. The moment my run synced automatically? That's when it clicked.",null,1782865071710]