[{"data":1,"prerenderedAt":2315},["ShallowReactive",2],{"portfolio":3},[4,301,547,1006,1268,1942,2153],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":8,"body":10,"_type":295,"_id":296,"_source":297,"_file":298,"_stem":299,"_extension":300},"\u002Fportfolio\u002Ffreshmarket-platform","portfolio",false,"","Freshmarket Platform",{"type":11,"children":12,"toc":285},"root",[13,18,42,47,53,58,63,83,95,107,113,125,130,253,267,273],{"type":14,"tag":15,"props":16,"children":17},"element","hr",{},[],{"type":14,"tag":19,"props":20,"children":22},"h2",{"id":21},"title-freshmarket-แพลตฟอร์มตลาดสดออนไลน์excerpt-ออกแบบและพัฒนาแพลตฟอร์ม-e-commerce-สำหรับตลาดสดที่เชื่อมต่อเกษตรกรโดยตรงกับผู้บริโภค-ยอดขายเติบโต-3-เท่าใน-6-เดือนcategory-e-commerceclient-freshmarket-co-ltdtimeline-10-สัปดาห์role-full-stack-development-uiux-design-devopsdate-2024-08-15url-httpsfreshmarketexamplecomtags-nextjs-typescript-postgresql-prisma-2c2p-tailwind-css-cloudflare",[23,26,34,36],{"type":24,"value":25},"text","title: FreshMarket — แพลตฟอร์มตลาดสดออนไลน์\nexcerpt: ออกแบบและพัฒนาแพลตฟอร์ม e-commerce สำหรับตลาดสดที่เชื่อมต่อเกษตรกรโดยตรงกับผู้บริโภค ยอดขายเติบโต 3 เท่าใน 6 เดือน\ncategory: E-commerce\nclient: FreshMarket Co., Ltd.\ntimeline: 10 สัปดาห์\nrole: Full-stack development, UI\u002FUX Design, DevOps\ndate: 2024-08-15\nurl: ",{"type":14,"tag":27,"props":28,"children":32},"a",{"href":29,"rel":30},"https:\u002F\u002Ffreshmarket.example.com",[31],"nofollow",[33],{"type":24,"value":29},{"type":24,"value":35},"\ntags: ",{"type":14,"tag":37,"props":38,"children":39},"span",{},[40],{"type":24,"value":41},"Next.js, TypeScript, PostgreSQL, Prisma, 2C2P, Tailwind CSS, Cloudflare",{"type":14,"tag":19,"props":43,"children":45},{"id":44},"โจทย์ที่เราได้รับ",[46],{"type":24,"value":44},{"type":14,"tag":48,"props":49,"children":50},"p",{},[51],{"type":24,"value":52},"FreshMarket ต้องการแพลตฟอร์มที่เชื่อมต่อเกษตรกรในชนบทกับผู้บริโภคในเมืองโดยตรง โดยไม่ผ่านคนกลาง เป้าหมายหลักคือลดค่าใช้จ่ายให้เกษตรกร และให้ผู้บริโภคได้สินค้าสดกว่าและราคาถูกกว่าในซูเปอร์มาร์เก็ตทั่วไป",{"type":14,"tag":48,"props":54,"children":55},{},[56],{"type":24,"value":57},"ความท้าทายหลักมีสองประการ: หนึ่ง — ระบบต้องรองรับสินค้าที่มีสต็อกแปรผันตามฤดูกาลและราคาที่เปลี่ยนได้ทุกวัน สอง — กลุ่มผู้ใช้ฝั่งเกษตรกรมีความคุ้นเคยกับสมาร์ทโฟนในระดับพื้นฐาน UI จึงต้องง่ายมาก",{"type":14,"tag":19,"props":59,"children":61},{"id":60},"วิธีที่เราแก้ปัญหา",[62],{"type":24,"value":60},{"type":14,"tag":48,"props":64,"children":65},{},[66,68,74,76,81],{"type":24,"value":67},"เราเลือก ",{"type":14,"tag":69,"props":70,"children":71},"strong",{},[72],{"type":24,"value":73},"Next.js 14",{"type":24,"value":75}," สำหรับ frontend เพราะต้องการ SSR ที่ดีเพื่อ SEO ในหน้า product listing และ ",{"type":14,"tag":69,"props":77,"children":78},{},[79],{"type":24,"value":80},"PostgreSQL + Prisma",{"type":24,"value":82}," สำหรับ database เพราะ schema ของ e-commerce มีความสัมพันธ์ซับซ้อนที่ relational database จัดการได้ดีกว่า",{"type":14,"tag":48,"props":84,"children":85},{},[86,88,93],{"type":24,"value":87},"สำหรับ payment gateway เราเลือก ",{"type":14,"tag":69,"props":89,"children":90},{},[91],{"type":24,"value":92},"2C2P",{"type":24,"value":94}," เพราะรองรับ PromptPay, True Money Wallet และบัตรเครดิต ซึ่งเป็น payment method หลักของกลุ่มเป้าหมาย",{"type":14,"tag":48,"props":96,"children":97},{},[98,100,105],{"type":24,"value":99},"ส่วน inventory system เราออกแบบให้เป็น ",{"type":14,"tag":69,"props":101,"children":102},{},[103],{"type":24,"value":104},"event-sourced",{"type":24,"value":106}," เพื่อให้ติดตาม stock movement ได้แม่นยำและมี audit trail สำหรับการคืนสินค้า",{"type":14,"tag":19,"props":108,"children":110},{"id":109},"สิ่งที่ทำให้-project-นี้น่าสนใจ",[111],{"type":24,"value":112},"สิ่งที่ทำให้ Project นี้น่าสนใจ",{"type":14,"tag":48,"props":114,"children":115},{},[116,118,123],{"type":24,"value":117},"การออกแบบ UI สำหรับสองกลุ่มผู้ใช้ที่แตกต่างกันมากเป็นความท้าทายที่น่าสนใจ เราทำ ",{"type":14,"tag":69,"props":119,"children":120},{},[121],{"type":24,"value":122},"separate app shells",{"type":24,"value":124}," — หน้า seller dashboard ใช้ navigation pattern ที่เรียบง่ายมาก ฟอนต์ใหญ่ ปุ่มใหญ่ เพื่อให้เกษตรกรที่ใช้มือถือกลางแจ้งกดได้ง่าย ขณะที่ buyer experience เน้น discovery และ browsing ที่ลื่นไหล",{"type":14,"tag":19,"props":126,"children":128},{"id":127},"ผลลัพธ์",[129],{"type":24,"value":127},{"type":14,"tag":131,"props":132,"children":133},"table",{},[134,158],{"type":14,"tag":135,"props":136,"children":137},"thead",{},[138],{"type":14,"tag":139,"props":140,"children":141},"tr",{},[142,148,153],{"type":14,"tag":143,"props":144,"children":145},"th",{},[146],{"type":24,"value":147},"Metric",{"type":14,"tag":143,"props":149,"children":150},{},[151],{"type":24,"value":152},"ก่อน",{"type":14,"tag":143,"props":154,"children":155},{},[156],{"type":24,"value":157},"หลัง 6 เดือน",{"type":14,"tag":159,"props":160,"children":161},"tbody",{},[162,181,199,217,235],{"type":14,"tag":139,"props":163,"children":164},{},[165,171,176],{"type":14,"tag":166,"props":167,"children":168},"td",{},[169],{"type":24,"value":170},"GMV รายเดือน",{"type":14,"tag":166,"props":172,"children":173},{},[174],{"type":24,"value":175},"450,000 บาท",{"type":14,"tag":166,"props":177,"children":178},{},[179],{"type":24,"value":180},"1,350,000 บาท",{"type":14,"tag":139,"props":182,"children":183},{},[184,189,194],{"type":14,"tag":166,"props":185,"children":186},{},[187],{"type":24,"value":188},"เกษตรกรที่ลงทะเบียน",{"type":14,"tag":166,"props":190,"children":191},{},[192],{"type":24,"value":193},"23 ราย",{"type":14,"tag":166,"props":195,"children":196},{},[197],{"type":24,"value":198},"187 ราย",{"type":14,"tag":139,"props":200,"children":201},{},[202,207,212],{"type":14,"tag":166,"props":203,"children":204},{},[205],{"type":24,"value":206},"Conversion rate",{"type":14,"tag":166,"props":208,"children":209},{},[210],{"type":24,"value":211},"1.8%",{"type":14,"tag":166,"props":213,"children":214},{},[215],{"type":24,"value":216},"4.2%",{"type":14,"tag":139,"props":218,"children":219},{},[220,225,230],{"type":14,"tag":166,"props":221,"children":222},{},[223],{"type":24,"value":224},"Lighthouse Performance",{"type":14,"tag":166,"props":226,"children":227},{},[228],{"type":24,"value":229},"—",{"type":14,"tag":166,"props":231,"children":232},{},[233],{"type":24,"value":234},"97",{"type":14,"tag":139,"props":236,"children":237},{},[238,243,248],{"type":14,"tag":166,"props":239,"children":240},{},[241],{"type":24,"value":242},"Average order value",{"type":14,"tag":166,"props":244,"children":245},{},[246],{"type":24,"value":247},"285 บาท",{"type":14,"tag":166,"props":249,"children":250},{},[251],{"type":24,"value":252},"410 บาท",{"type":14,"tag":254,"props":255,"children":256},"blockquote",{},[257,262],{"type":14,"tag":48,"props":258,"children":259},{},[260],{"type":24,"value":261},"\"Venoct เข้าใจ business ของเราจริงๆ ไม่ใช่แค่ทำตาม spec แต่ช่วยคิด solution ที่ดีกว่าที่เราขอไปด้วย\"",{"type":14,"tag":48,"props":263,"children":264},{},[265],{"type":24,"value":266},"— คุณสมชาย วงษ์เกษตร, CEO FreshMarket",{"type":14,"tag":19,"props":268,"children":270},{"id":269},"lessons-learned",[271],{"type":24,"value":272},"Lessons Learned",{"type":14,"tag":48,"props":274,"children":275},{},[276,278,283],{"type":24,"value":277},"โปรเจกต์นี้สอนให้เราเห็นว่า ",{"type":14,"tag":69,"props":279,"children":280},{},[281],{"type":24,"value":282},"user research ที่ดีสามารถเปลี่ยน solution ทั้งหมดได้",{"type":24,"value":284}," เราเคย plan ว่าจะทำ mobile app แต่หลังจากสัมภาษณ์เกษตรกร 15 ราย พบว่าพวกเขาสะดวกใช้ LINE มากกว่าแอปใหม่ เราจึงเพิ่ม LINE Notify integration เพื่อแจ้งเตือนออเดอร์ใหม่ ซึ่งลด onboarding friction ลงได้มาก",{"title":8,"searchDepth":286,"depth":286,"links":287},2,[288,290,291,292,293,294],{"id":21,"depth":286,"text":289},"title: FreshMarket — แพลตฟอร์มตลาดสดออนไลน์\nexcerpt: ออกแบบและพัฒนาแพลตฟอร์ม e-commerce สำหรับตลาดสดที่เชื่อมต่อเกษตรกรโดยตรงกับผู้บริโภค ยอดขายเติบโต 3 เท่าใน 6 เดือน\ncategory: E-commerce\nclient: FreshMarket Co., Ltd.\ntimeline: 10 สัปดาห์\nrole: Full-stack development, UI\u002FUX Design, DevOps\ndate: 2024-08-15\nurl: https:\u002F\u002Ffreshmarket.example.com\ntags: Next.js, TypeScript, PostgreSQL, Prisma, 2C2P, Tailwind CSS, Cloudflare",{"id":44,"depth":286,"text":44},{"id":60,"depth":286,"text":60},{"id":109,"depth":286,"text":112},{"id":127,"depth":286,"text":127},{"id":269,"depth":286,"text":272},"markdown","content:portfolio:freshmarket-platform.md","content","portfolio\u002Ffreshmarket-platform.md","portfolio\u002Ffreshmarket-platform","md",{"_path":302,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":303,"description":8,"category":304,"client":305,"timeline":306,"role":307,"date":308,"url":309,"tags":310,"body":318,"_type":295,"_id":544,"_source":297,"_file":545,"_stem":546,"_extension":300},"\u002Fportfolio\u002Fzenvoy-saas-dashboard","Zenvoy — แพลตฟอร์ม SaaS สำหรับบริหารทีม Remote","SaaS","Zenvoy Technologies","14 สัปดาห์","Full-stack development, Product Design, System Architecture","2024-11-20","https:\u002F\u002Fzenvoy.example.com",[311,312,313,314,315,316,317],"Nuxt 3","TypeScript","Supabase","Tailwind CSS","Chart.js","Stripe","Vercel",{"type":11,"children":319,"toc":537},[320,324,329,334,338,354,359,371,375,396,408,412,508,521,525],{"type":14,"tag":19,"props":321,"children":322},{"id":44},[323],{"type":24,"value":44},{"type":14,"tag":48,"props":325,"children":326},{},[327],{"type":24,"value":328},"Zenvoy ต้องการแทนที่ระบบ spreadsheet + Line group ที่ใช้บริหารทีม Remote อยู่เดิม ด้วย web app ที่ HR และผู้จัดการสามารถเห็นภาพรวมทีมได้แบบ real-time รองรับพนักงานทั้งในไทยและต่างประเทศ และเชื่อมกับ payroll provider ที่ใช้อยู่แล้วได้",{"type":14,"tag":48,"props":330,"children":331},{},[332],{"type":24,"value":333},"ความท้าทายคือต้องรองรับ timezone หลายโซน, สกุลเงินหลายสกุล และ permission matrix ที่ซับซ้อน (Employee \u002F Team Lead \u002F HR \u002F Admin \u002F Super Admin)",{"type":14,"tag":19,"props":335,"children":336},{"id":60},[337],{"type":24,"value":60},{"type":14,"tag":48,"props":339,"children":340},{},[341,342,346,348,352],{"type":24,"value":67},{"type":14,"tag":69,"props":343,"children":344},{},[345],{"type":24,"value":311},{"type":24,"value":347}," เป็น frontend และ ",{"type":14,"tag":69,"props":349,"children":350},{},[351],{"type":24,"value":313},{"type":24,"value":353}," เป็น backend เพราะ Supabase RLS (Row-Level Security) ทำให้ implement permission matrix ได้ตรงไปตรงมา โดยไม่ต้องเขียน middleware จำนวนมาก",{"type":14,"tag":48,"props":355,"children":356},{},[357],{"type":24,"value":358},"สำหรับ real-time features เช่น attendance tracking และ notification เราใช้ Supabase Realtime Channels ซึ่งลด complexity ได้มากเมื่อเทียบกับ WebSocket server แยก",{"type":14,"tag":48,"props":360,"children":361},{},[362,364,369],{"type":24,"value":363},"Billing ใช้ ",{"type":14,"tag":69,"props":365,"children":366},{},[367],{"type":24,"value":368},"Stripe Billing",{"type":24,"value":370}," แบบ per-seat subscription พร้อม grace period และ dunning management",{"type":14,"tag":19,"props":372,"children":373},{"id":109},[374],{"type":24,"value":112},{"type":14,"tag":48,"props":376,"children":377},{},[378,380,385,387,394],{"type":24,"value":379},"ส่วนที่ท้าทายที่สุดคือ ",{"type":14,"tag":69,"props":381,"children":382},{},[383],{"type":24,"value":384},"timezone-aware time tracking",{"type":24,"value":386}," พนักงานในกรุงเทพ, ลอนดอน และโตเกียวต้องเห็นเวลาในโซนของตัวเองเสมอ แต่ database ต้องเก็บ UTC ทั้งหมด เราออกแบบ composable ",{"type":14,"tag":388,"props":389,"children":391},"code",{"className":390},[],[392],{"type":24,"value":393},"useWorkHours",{"type":24,"value":395}," ที่จัดการ conversion อย่างโปร่งใส และเขียน unit test ครอบ edge case ต่างๆ เช่น DST transition และ midnight shift",{"type":14,"tag":48,"props":397,"children":398},{},[399,401,406],{"type":24,"value":400},"นอกจากนี้เราทำ ",{"type":14,"tag":69,"props":402,"children":403},{},[404],{"type":24,"value":405},"export pipeline",{"type":24,"value":407}," ที่สร้าง CSV\u002FExcel ตาม format ที่ payroll provider แต่ละรายต้องการ ซึ่งลด manual work ของ HR ลงได้มากที่สุด",{"type":14,"tag":19,"props":409,"children":410},{"id":127},[411],{"type":24,"value":127},{"type":14,"tag":131,"props":413,"children":414},{},[415,434],{"type":14,"tag":135,"props":416,"children":417},{},[418],{"type":14,"tag":139,"props":419,"children":420},{},[421,425,429],{"type":14,"tag":143,"props":422,"children":423},{},[424],{"type":24,"value":147},{"type":14,"tag":143,"props":426,"children":427},{},[428],{"type":24,"value":152},{"type":14,"tag":143,"props":430,"children":431},{},[432],{"type":24,"value":433},"หลัง 3 เดือน",{"type":14,"tag":159,"props":435,"children":436},{},[437,455,473,491],{"type":14,"tag":139,"props":438,"children":439},{},[440,445,450],{"type":14,"tag":166,"props":441,"children":442},{},[443],{"type":24,"value":444},"เวลา HR ต่อเดือน (ชั่วโมง)",{"type":14,"tag":166,"props":446,"children":447},{},[448],{"type":24,"value":449},"38 ชั่วโมง",{"type":14,"tag":166,"props":451,"children":452},{},[453],{"type":24,"value":454},"15 ชั่วโมง",{"type":14,"tag":139,"props":456,"children":457},{},[458,463,468],{"type":14,"tag":166,"props":459,"children":460},{},[461],{"type":24,"value":462},"ข้อผิดพลาดของ payroll",{"type":14,"tag":166,"props":464,"children":465},{},[466],{"type":24,"value":467},"4–6 ครั้ง\u002Fเดือน",{"type":14,"tag":166,"props":469,"children":470},{},[471],{"type":24,"value":472},"0 ครั้ง",{"type":14,"tag":139,"props":474,"children":475},{},[476,481,486],{"type":14,"tag":166,"props":477,"children":478},{},[479],{"type":24,"value":480},"Onboarding พนักงานใหม่",{"type":14,"tag":166,"props":482,"children":483},{},[484],{"type":24,"value":485},"2 วัน",{"type":14,"tag":166,"props":487,"children":488},{},[489],{"type":24,"value":490},"30 นาที",{"type":14,"tag":139,"props":492,"children":493},{},[494,499,503],{"type":14,"tag":166,"props":495,"children":496},{},[497],{"type":24,"value":498},"NPS Score จากทีม HR",{"type":14,"tag":166,"props":500,"children":501},{},[502],{"type":24,"value":229},{"type":14,"tag":166,"props":504,"children":505},{},[506],{"type":24,"value":507},"78",{"type":14,"tag":254,"props":509,"children":510},{},[511,516],{"type":14,"tag":48,"props":512,"children":513},{},[514],{"type":24,"value":515},"\"ก่อนหน้านี้ปิด payroll แต่ละเดือนใช้เวลาทั้งสัปดาห์ ตอนนี้แค่กดปุ่มเดียว Venoct ช่วยเราได้จริงๆ\"",{"type":14,"tag":48,"props":517,"children":518},{},[519],{"type":24,"value":520},"— คุณนภา ชุมพูทอง, Head of People, Zenvoy Technologies",{"type":14,"tag":19,"props":522,"children":523},{"id":269},[524],{"type":24,"value":272},{"type":14,"tag":48,"props":526,"children":527},{},[528,530,535],{"type":24,"value":529},"โปรเจกต์นี้ทำให้เรามั่นใจมากขึ้นว่า ",{"type":14,"tag":69,"props":531,"children":532},{},[533],{"type":24,"value":534},"Supabase RLS เป็น pattern ที่ scale ได้ดีสำหรับ multi-tenant SaaS",{"type":24,"value":536}," เพียงแต่ต้องออกแบบ policy ให้รัดกุมตั้งแต่ต้น เพราะการแก้ไข policy ย้อนหลังบน production table ที่มีข้อมูลเยอะนั้น risky มาก เราเลยพัฒนา checklist สำหรับ RLS design ที่ใช้ในทุกโปรเจกต์ SaaS ตั้งแต่นั้นมา",{"title":8,"searchDepth":286,"depth":286,"links":538},[539,540,541,542,543],{"id":44,"depth":286,"text":44},{"id":60,"depth":286,"text":60},{"id":109,"depth":286,"text":112},{"id":127,"depth":286,"text":127},{"id":269,"depth":286,"text":272},"content:portfolio:zenvoy-saas-dashboard.md","portfolio\u002Fzenvoy-saas-dashboard.md","portfolio\u002Fzenvoy-saas-dashboard",{"_path":548,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":549,"description":8,"category":304,"client":550,"timeline":306,"role":551,"date":552,"tags":553,"body":557,"_type":295,"_id":1003,"_source":297,"_file":1004,"_stem":1005,"_extension":300},"\u002Fportfolio\u002Fmedsync-dashboard","MedSync — ระบบจัดการคลินิกและนัดหมายผู้ป่วย","MedSync Thailand","Full-stack development, System Architecture, UI\u002FUX","2024-06-20",[554,311,312,313,555,314,556],"Vue 3","PostgreSQL","Pinia",{"type":11,"children":558,"toc":997},[559,564,569,574,585,605,615,633,639,644,663,918,922,978,991],{"type":14,"tag":19,"props":560,"children":562},{"id":561},"ภาพรวมโปรเจกต์",[563],{"type":24,"value":561},{"type":14,"tag":48,"props":565,"children":566},{},[567],{"type":24,"value":568},"MedSync เป็น SaaS platform สำหรับคลินิกทั่วไปและคลินิกเฉพาะทางขนาดเล็กถึงกลาง เป้าหมายหลักคือลดภาระงานธุรการ เช่น การจัดตารางนัด การบันทึกประวัติผู้ป่วย และการออกเอกสารทางการแพทย์ ที่ยังทำด้วย Excel และกระดาษในคลินิกส่วนใหญ่",{"type":14,"tag":19,"props":570,"children":572},{"id":571},"สถาปัตยกรรมที่เราเลือก",[573],{"type":24,"value":571},{"type":14,"tag":48,"props":575,"children":576},{},[577,578,583],{"type":24,"value":67},{"type":14,"tag":69,"props":579,"children":580},{},[581],{"type":24,"value":582},"Nuxt 3 + Vue 3",{"type":24,"value":584}," เพราะ:",{"type":14,"tag":586,"props":587,"children":588},"ul",{},[589,595,600],{"type":14,"tag":590,"props":591,"children":592},"li",{},[593],{"type":24,"value":594},"Composition API ทำให้ reuse logic ระหว่าง components ได้ง่าย",{"type":14,"tag":590,"props":596,"children":597},{},[598],{"type":24,"value":599},"Built-in SSR สำหรับหน้า public (landing page, pricing)",{"type":14,"tag":590,"props":601,"children":602},{},[603],{"type":24,"value":604},"TypeScript support ดีเยี่ยม ซึ่งสำคัญมากสำหรับ healthcare data",{"type":14,"tag":48,"props":606,"children":607},{},[608,610,614],{"type":24,"value":609},"สำหรับ backend เลือก ",{"type":14,"tag":69,"props":611,"children":612},{},[613],{"type":24,"value":313},{"type":24,"value":584},{"type":14,"tag":586,"props":616,"children":617},{},[618,623,628],{"type":14,"tag":590,"props":619,"children":620},{},[621],{"type":24,"value":622},"Row Level Security ทำให้ isolate ข้อมูลระหว่างคลินิกได้ง่าย",{"type":14,"tag":590,"props":624,"children":625},{},[626],{"type":24,"value":627},"Realtime subscriptions สำหรับ live appointment board",{"type":14,"tag":590,"props":629,"children":630},{},[631],{"type":24,"value":632},"Auth ที่ครบ พร้อม JWT ที่ integrate กับ Vue ได้ตรงไปตรงมา",{"type":14,"tag":19,"props":634,"children":636},{"id":635},"ความท้าทายหลัก-realtime-appointment-board",[637],{"type":24,"value":638},"ความท้าทายหลัก: Realtime Appointment Board",{"type":14,"tag":48,"props":640,"children":641},{},[642],{"type":24,"value":643},"ฟีเจอร์ที่ยากที่สุดคือ appointment board ที่ต้องอัปเดต realtime เมื่อนัดถูกสร้าง เลื่อน หรือยกเลิก โดยไม่กระทบ performance ของหน้าอื่น",{"type":14,"tag":48,"props":645,"children":646},{},[647,649,654,656,661],{"type":24,"value":648},"เราใช้ ",{"type":14,"tag":69,"props":650,"children":651},{},[652],{"type":24,"value":653},"Supabase Realtime Channels",{"type":24,"value":655}," ร่วมกับ ",{"type":14,"tag":69,"props":657,"children":658},{},[659],{"type":24,"value":660},"Pinia store",{"type":24,"value":662}," ออกแบบให้ events จาก websocket ไป mutate store โดยตรง โดยไม่ต้อง refetch ข้อมูลทั้งหมด",{"type":14,"tag":664,"props":665,"children":669},"pre",{"className":666,"code":667,"language":668,"meta":8,"style":8},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F composables\u002FuseAppointments.ts\nconst channel = supabase.channel('appointments')\n  .on('postgres_changes', {\n    event: '*',\n    schema: 'public',\n    table: 'appointments',\n    filter: `clinic_id=eq.${clinicId}`,\n  }, (payload) => {\n    appointmentStore.handleRealtimeEvent(payload)\n  })\n  .subscribe()\n","typescript",[670],{"type":14,"tag":388,"props":671,"children":672},{"__ignoreMap":8},[673,684,732,760,779,797,814,842,872,891,900],{"type":14,"tag":37,"props":674,"children":677},{"class":675,"line":676},"line",1,[678],{"type":14,"tag":37,"props":679,"children":681},{"style":680},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[682],{"type":24,"value":683},"\u002F\u002F composables\u002FuseAppointments.ts\n",{"type":14,"tag":37,"props":685,"children":686},{"class":675,"line":286},[687,693,699,704,710,716,721,727],{"type":14,"tag":37,"props":688,"children":690},{"style":689},"--shiki-default:#D73A49;--shiki-dark:#F97583",[691],{"type":24,"value":692},"const",{"type":14,"tag":37,"props":694,"children":696},{"style":695},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[697],{"type":24,"value":698}," channel",{"type":14,"tag":37,"props":700,"children":701},{"style":689},[702],{"type":24,"value":703}," =",{"type":14,"tag":37,"props":705,"children":707},{"style":706},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[708],{"type":24,"value":709}," supabase.",{"type":14,"tag":37,"props":711,"children":713},{"style":712},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[714],{"type":24,"value":715},"channel",{"type":14,"tag":37,"props":717,"children":718},{"style":706},[719],{"type":24,"value":720},"(",{"type":14,"tag":37,"props":722,"children":724},{"style":723},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[725],{"type":24,"value":726},"'appointments'",{"type":14,"tag":37,"props":728,"children":729},{"style":706},[730],{"type":24,"value":731},")\n",{"type":14,"tag":37,"props":733,"children":735},{"class":675,"line":734},3,[736,741,746,750,755],{"type":14,"tag":37,"props":737,"children":738},{"style":706},[739],{"type":24,"value":740},"  .",{"type":14,"tag":37,"props":742,"children":743},{"style":712},[744],{"type":24,"value":745},"on",{"type":14,"tag":37,"props":747,"children":748},{"style":706},[749],{"type":24,"value":720},{"type":14,"tag":37,"props":751,"children":752},{"style":723},[753],{"type":24,"value":754},"'postgres_changes'",{"type":14,"tag":37,"props":756,"children":757},{"style":706},[758],{"type":24,"value":759},", {\n",{"type":14,"tag":37,"props":761,"children":763},{"class":675,"line":762},4,[764,769,774],{"type":14,"tag":37,"props":765,"children":766},{"style":706},[767],{"type":24,"value":768},"    event: ",{"type":14,"tag":37,"props":770,"children":771},{"style":723},[772],{"type":24,"value":773},"'*'",{"type":14,"tag":37,"props":775,"children":776},{"style":706},[777],{"type":24,"value":778},",\n",{"type":14,"tag":37,"props":780,"children":782},{"class":675,"line":781},5,[783,788,793],{"type":14,"tag":37,"props":784,"children":785},{"style":706},[786],{"type":24,"value":787},"    schema: ",{"type":14,"tag":37,"props":789,"children":790},{"style":723},[791],{"type":24,"value":792},"'public'",{"type":14,"tag":37,"props":794,"children":795},{"style":706},[796],{"type":24,"value":778},{"type":14,"tag":37,"props":798,"children":800},{"class":675,"line":799},6,[801,806,810],{"type":14,"tag":37,"props":802,"children":803},{"style":706},[804],{"type":24,"value":805},"    table: ",{"type":14,"tag":37,"props":807,"children":808},{"style":723},[809],{"type":24,"value":726},{"type":14,"tag":37,"props":811,"children":812},{"style":706},[813],{"type":24,"value":778},{"type":14,"tag":37,"props":815,"children":817},{"class":675,"line":816},7,[818,823,828,833,838],{"type":14,"tag":37,"props":819,"children":820},{"style":706},[821],{"type":24,"value":822},"    filter: ",{"type":14,"tag":37,"props":824,"children":825},{"style":723},[826],{"type":24,"value":827},"`clinic_id=eq.${",{"type":14,"tag":37,"props":829,"children":830},{"style":706},[831],{"type":24,"value":832},"clinicId",{"type":14,"tag":37,"props":834,"children":835},{"style":723},[836],{"type":24,"value":837},"}`",{"type":14,"tag":37,"props":839,"children":840},{"style":706},[841],{"type":24,"value":778},{"type":14,"tag":37,"props":843,"children":845},{"class":675,"line":844},8,[846,851,857,862,867],{"type":14,"tag":37,"props":847,"children":848},{"style":706},[849],{"type":24,"value":850},"  }, (",{"type":14,"tag":37,"props":852,"children":854},{"style":853},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[855],{"type":24,"value":856},"payload",{"type":14,"tag":37,"props":858,"children":859},{"style":706},[860],{"type":24,"value":861},") ",{"type":14,"tag":37,"props":863,"children":864},{"style":689},[865],{"type":24,"value":866},"=>",{"type":14,"tag":37,"props":868,"children":869},{"style":706},[870],{"type":24,"value":871}," {\n",{"type":14,"tag":37,"props":873,"children":875},{"class":675,"line":874},9,[876,881,886],{"type":14,"tag":37,"props":877,"children":878},{"style":706},[879],{"type":24,"value":880},"    appointmentStore.",{"type":14,"tag":37,"props":882,"children":883},{"style":712},[884],{"type":24,"value":885},"handleRealtimeEvent",{"type":14,"tag":37,"props":887,"children":888},{"style":706},[889],{"type":24,"value":890},"(payload)\n",{"type":14,"tag":37,"props":892,"children":894},{"class":675,"line":893},10,[895],{"type":14,"tag":37,"props":896,"children":897},{"style":706},[898],{"type":24,"value":899},"  })\n",{"type":14,"tag":37,"props":901,"children":903},{"class":675,"line":902},11,[904,908,913],{"type":14,"tag":37,"props":905,"children":906},{"style":706},[907],{"type":24,"value":740},{"type":14,"tag":37,"props":909,"children":910},{"style":712},[911],{"type":24,"value":912},"subscribe",{"type":14,"tag":37,"props":914,"children":915},{"style":706},[916],{"type":24,"value":917},"()\n",{"type":14,"tag":19,"props":919,"children":920},{"id":127},[921],{"type":24,"value":127},{"type":14,"tag":586,"props":923,"children":924},{},[925,937,949,961],{"type":14,"tag":590,"props":926,"children":927},{},[928,930,935],{"type":24,"value":929},"เวลาที่ใช้ในการจัดตารางนัดลดจาก ",{"type":14,"tag":69,"props":931,"children":932},{},[933],{"type":24,"value":934},"12 นาที → 2 นาที",{"type":24,"value":936}," ต่อผู้ป่วย",{"type":14,"tag":590,"props":938,"children":939},{},[940,942,947],{"type":24,"value":941},"Error rate ในการออกใบเสร็จลดลง ",{"type":14,"tag":69,"props":943,"children":944},{},[945],{"type":24,"value":946},"95%",{"type":24,"value":948}," (จาก manual entry)",{"type":14,"tag":590,"props":950,"children":951},{},[952,954,959],{"type":24,"value":953},"Staff satisfaction score ",{"type":14,"tag":69,"props":955,"children":956},{},[957],{"type":24,"value":958},"4.6\u002F5",{"type":24,"value":960}," (จาก 8 คลินิก pilot)",{"type":14,"tag":590,"props":962,"children":963},{},[964,966,971,973],{"type":24,"value":965},"Lighthouse score: Performance ",{"type":14,"tag":69,"props":967,"children":968},{},[969],{"type":24,"value":970},"94",{"type":24,"value":972},", Accessibility ",{"type":14,"tag":69,"props":974,"children":975},{},[976],{"type":24,"value":977},"98",{"type":14,"tag":254,"props":979,"children":980},{},[981,986],{"type":14,"tag":48,"props":982,"children":983},{},[984],{"type":24,"value":985},"\"ก่อนหน้านี้ reception ต้องเปิด Excel หลายไฟล์พร้อมกัน ตอนนี้ทุกอย่างอยู่ในหน้าเดียว\"",{"type":14,"tag":48,"props":987,"children":988},{},[989],{"type":24,"value":990},"— คุณพรรณี สุขสวัสดิ์, ผู้จัดการคลินิก",{"type":14,"tag":992,"props":993,"children":994},"style",{},[995],{"type":24,"value":996},"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);}",{"title":8,"searchDepth":286,"depth":286,"links":998},[999,1000,1001,1002],{"id":561,"depth":286,"text":561},{"id":571,"depth":286,"text":571},{"id":635,"depth":286,"text":638},{"id":127,"depth":286,"text":127},"content:portfolio:medsync-dashboard.md","portfolio\u002Fmedsync-dashboard.md","portfolio\u002Fmedsync-dashboard",{"_path":1007,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":1008,"description":8,"category":1009,"client":1010,"timeline":1011,"role":1012,"date":1013,"tags":1014,"body":1022,"_type":295,"_id":1265,"_source":297,"_file":1266,"_stem":1267,"_extension":300},"\u002Fportfolio\u002Frunclub-app","RunClub — แอปสำหรับชมรมวิ่งและ community นักวิ่งไทย","Mobile","RunClub Thailand","12 สัปดาห์","Mobile Development, UI\u002FUX, Backend API","2024-04-10",[1015,1016,1017,1018,1019,1020,1021],"Flutter","Dart","Firebase","Google Maps","Node.js","Express","MongoDB",{"type":11,"children":1023,"toc":1254},[1024,1029,1034,1040,1047,1060,1066,1071,1077,1082,1088,1093,1135,1139,1237,1242],{"type":14,"tag":19,"props":1025,"children":1027},{"id":1026},"ที่มาของโปรเจกต์",[1028],{"type":24,"value":1026},{"type":14,"tag":48,"props":1030,"children":1031},{},[1032],{"type":24,"value":1033},"RunClub เริ่มจาก LINE group นักวิ่งที่มีสมาชิก 3,000+ คน แต่การจัดกิจกรรม ติดตามผล และสร้าง community ผ่าน LINE เริ่มไม่เพียงพอ ลูกค้าต้องการแอปที่ทำให้ running experience เชื่อมต่อกับ community ได้จริง",{"type":14,"tag":19,"props":1035,"children":1037},{"id":1036},"features-หลัก",[1038],{"type":24,"value":1039},"Features หลัก",{"type":14,"tag":1041,"props":1042,"children":1044},"h3",{"id":1043},"gps-activity-tracking",[1045],{"type":24,"value":1046},"GPS Activity Tracking",{"type":14,"tag":48,"props":1048,"children":1049},{},[1050,1052,1058],{"type":24,"value":1051},"ใช้ ",{"type":14,"tag":388,"props":1053,"children":1055},{"className":1054},[],[1056],{"type":24,"value":1057},"geolocator",{"type":24,"value":1059}," package ร่วมกับ Kalman filter เพื่อลด GPS noise ในพื้นที่ที่มีตึกสูง — ปัญหาหลักของนักวิ่งในกรุงเทพฯ",{"type":14,"tag":1041,"props":1061,"children":1063},{"id":1062},"challenge-system",[1064],{"type":24,"value":1065},"Challenge System",{"type":14,"tag":48,"props":1067,"children":1068},{},[1069],{"type":24,"value":1070},"ระบบ challenge ที่ผู้ใช้สร้างเองได้ เช่น \"วิ่ง 100 กม. ใน 30 วัน\" พร้อม leaderboard realtime และ badge rewards",{"type":14,"tag":1041,"props":1072,"children":1074},{"id":1073},"social-feed",[1075],{"type":24,"value":1076},"Social Feed",{"type":14,"tag":48,"props":1078,"children":1079},{},[1080],{"type":24,"value":1081},"Feed ที่แสดง activity ของ friends พร้อม kudos system (คล้าย Strava แต่เน้น community ท้องถิ่น)",{"type":14,"tag":19,"props":1083,"children":1085},{"id":1084},"ความท้าทายด้าน-performance",[1086],{"type":24,"value":1087},"ความท้าทายด้าน Performance",{"type":14,"tag":48,"props":1089,"children":1090},{},[1091],{"type":24,"value":1092},"การแสดงผล map route หลังวิ่งเสร็จเป็นจุดที่ต้องปรับแต่ง performance มากที่สุด เพราะ polyline จาก GPS อาจมี 10,000+ จุด เราแก้ด้วย:",{"type":14,"tag":1094,"props":1095,"children":1096},"ol",{},[1097,1107,1117],{"type":14,"tag":590,"props":1098,"children":1099},{},[1100,1105],{"type":14,"tag":69,"props":1101,"children":1102},{},[1103],{"type":24,"value":1104},"Douglas-Peucker algorithm",{"type":24,"value":1106}," เพื่อ simplify route ก่อน render",{"type":14,"tag":590,"props":1108,"children":1109},{},[1110,1115],{"type":14,"tag":69,"props":1111,"children":1112},{},[1113],{"type":24,"value":1114},"Lazy loading",{"type":24,"value":1116}," สำหรับ activity history ด้วย pagination",{"type":14,"tag":590,"props":1118,"children":1119},{},[1120,1125,1127,1133],{"type":14,"tag":69,"props":1121,"children":1122},{},[1123],{"type":24,"value":1124},"Image caching",{"type":24,"value":1126}," ด้วย ",{"type":14,"tag":388,"props":1128,"children":1130},{"className":1129},[],[1131],{"type":24,"value":1132},"cached_network_image",{"type":24,"value":1134}," สำหรับ profile pictures",{"type":14,"tag":19,"props":1136,"children":1137},{"id":127},[1138],{"type":24,"value":127},{"type":14,"tag":131,"props":1140,"children":1141},{},[1142,1162],{"type":14,"tag":135,"props":1143,"children":1144},{},[1145],{"type":14,"tag":139,"props":1146,"children":1147},{},[1148,1152,1157],{"type":14,"tag":143,"props":1149,"children":1150},{},[1151],{"type":24,"value":147},{"type":14,"tag":143,"props":1153,"children":1154},{},[1155],{"type":24,"value":1156},"เป้าหมาย",{"type":14,"tag":143,"props":1158,"children":1159},{},[1160],{"type":24,"value":1161},"ผล 3 เดือน",{"type":14,"tag":159,"props":1163,"children":1164},{},[1165,1183,1201,1219],{"type":14,"tag":139,"props":1166,"children":1167},{},[1168,1173,1178],{"type":14,"tag":166,"props":1169,"children":1170},{},[1171],{"type":24,"value":1172},"Daily Active Users",{"type":14,"tag":166,"props":1174,"children":1175},{},[1176],{"type":24,"value":1177},"2,000",{"type":14,"tag":166,"props":1179,"children":1180},{},[1181],{"type":24,"value":1182},"4,200",{"type":14,"tag":139,"props":1184,"children":1185},{},[1186,1191,1196],{"type":14,"tag":166,"props":1187,"children":1188},{},[1189],{"type":24,"value":1190},"Session duration",{"type":14,"tag":166,"props":1192,"children":1193},{},[1194],{"type":24,"value":1195},"8 นาที",{"type":14,"tag":166,"props":1197,"children":1198},{},[1199],{"type":24,"value":1200},"14 นาที",{"type":14,"tag":139,"props":1202,"children":1203},{},[1204,1209,1214],{"type":14,"tag":166,"props":1205,"children":1206},{},[1207],{"type":24,"value":1208},"App Store Rating",{"type":14,"tag":166,"props":1210,"children":1211},{},[1212],{"type":24,"value":1213},"4.0",{"type":14,"tag":166,"props":1215,"children":1216},{},[1217],{"type":24,"value":1218},"4.7 ⭐",{"type":14,"tag":139,"props":1220,"children":1221},{},[1222,1227,1232],{"type":14,"tag":166,"props":1223,"children":1224},{},[1225],{"type":24,"value":1226},"Crash-free rate",{"type":14,"tag":166,"props":1228,"children":1229},{},[1230],{"type":24,"value":1231},"99%",{"type":14,"tag":166,"props":1233,"children":1234},{},[1235],{"type":24,"value":1236},"99.6%",{"type":14,"tag":19,"props":1238,"children":1240},{"id":1239},"สิ่งที่ภูมิใจที่สุด",[1241],{"type":24,"value":1239},{"type":14,"tag":48,"props":1243,"children":1244},{},[1245,1247,1252],{"type":24,"value":1246},"การออกแบบ ",{"type":14,"tag":69,"props":1248,"children":1249},{},[1250],{"type":24,"value":1251},"onboarding flow",{"type":24,"value":1253}," ที่ทำให้ผู้ใช้ใหม่เห็น \"aha moment\" ได้เร็ว — เราออกแบบให้ผู้ใช้เห็น community ของ sub-district ตัวเองภายใน 30 วินาทีหลัง sign up โดยไม่ต้องค้นหาเอง ส่งผลให้ Day-7 retention อยู่ที่ 58% ซึ่งสูงกว่า benchmark ของ fitness app ทั่วไป",{"title":8,"searchDepth":286,"depth":286,"links":1255},[1256,1257,1262,1263,1264],{"id":1026,"depth":286,"text":1026},{"id":1036,"depth":286,"text":1039,"children":1258},[1259,1260,1261],{"id":1043,"depth":734,"text":1046},{"id":1062,"depth":734,"text":1065},{"id":1073,"depth":734,"text":1076},{"id":1084,"depth":286,"text":1087},{"id":127,"depth":286,"text":127},{"id":1239,"depth":286,"text":1239},"content:portfolio:runclub-app.md","portfolio\u002Frunclub-app.md","portfolio\u002Frunclub-app",{"_path":1269,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":1270,"description":8,"category":1271,"client":1272,"timeline":1273,"role":1274,"date":1275,"url":1276,"tags":1277,"body":1281,"_type":295,"_id":1939,"_source":297,"_file":1940,"_stem":1941,"_extension":300},"\u002Fportfolio\u002Fbaan-design-studio","Baan Design Studio — Portfolio Website สำหรับสตูดิโอออกแบบ","Web","Baan Design Studio","4 สัปดาห์","Frontend Development, Animation, Performance Optimization","2024-02-28","https:\u002F\u002Fbaan-design.example.com",[311,1278,1279,314,1280,312],"GSAP","Lenis","Cloudflare Pages",{"type":11,"children":1282,"toc":1929},[1283,1289,1294,1299,1305,1310,1316,1333,1576,1582,1587,1735,1741,1746,1795,1799,1912,1925],{"type":14,"tag":19,"props":1284,"children":1286},{"id":1285},"โจทย์-เว็บที่ต้องขายงานได้ด้วยตัวเอง",[1287],{"type":24,"value":1288},"โจทย์: เว็บที่ต้องขายงานได้ด้วยตัวเอง",{"type":14,"tag":48,"props":1290,"children":1291},{},[1292],{"type":24,"value":1293},"Baan Design Studio เป็นสตูดิโอออกแบบภายในที่มีผลงานโดดเด่น แต่เว็บไซต์เดิมทำให้ผลงานดูด้อยค่า พวกเขาต้องการเว็บที่ \"สวยพอๆ กับงานที่เราทำ\" และต้องโหลดเร็วพอที่จะไม่ทำให้ลูกค้าหนีก่อนเห็นงาน",{"type":14,"tag":19,"props":1295,"children":1297},{"id":1296},"สิ่งที่เราทำ",[1298],{"type":24,"value":1296},{"type":14,"tag":1041,"props":1300,"children":1302},{"id":1301},"animation-first-design-process",[1303],{"type":24,"value":1304},"Animation-first Design Process",{"type":14,"tag":48,"props":1306,"children":1307},{},[1308],{"type":24,"value":1309},"เราเริ่มจากการ prototype animation ใน CodePen ก่อน แล้วค่อย implement จริง เพราะ animation ที่ดีต้องออกแบบมาพร้อมกับ layout ไม่ใช่ add-on ทีหลัง",{"type":14,"tag":1041,"props":1311,"children":1313},{"id":1312},"horizontal-scroll-gallery",[1314],{"type":24,"value":1315},"Horizontal Scroll Gallery",{"type":14,"tag":48,"props":1317,"children":1318},{},[1319,1320,1325,1327,1331],{"type":24,"value":1051},{"type":14,"tag":69,"props":1321,"children":1322},{},[1323],{"type":24,"value":1324},"GSAP ScrollTrigger",{"type":24,"value":1326}," + ",{"type":14,"tag":69,"props":1328,"children":1329},{},[1330],{"type":24,"value":1279},{"type":24,"value":1332}," ทำ horizontal pinned section ที่ scroll ตาม vertical ได้ลื่นไหล:",{"type":14,"tag":664,"props":1334,"children":1338},{"className":1335,"code":1336,"language":1337,"meta":8,"style":8},"language-javascript shiki shiki-themes github-light github-dark","gsap.to('.gallery-track', {\n  x: () => -(gallery.scrollWidth - window.innerWidth),\n  ease: 'none',\n  scrollTrigger: {\n    trigger: '.gallery-section',\n    start: 'top top',\n    end: () => `+=${gallery.scrollWidth - window.innerWidth}`,\n    pin: true,\n    scrub: 1,\n  },\n})\n","javascript",[1339],{"type":14,"tag":388,"props":1340,"children":1341},{"__ignoreMap":8},[1342,1368,1405,1422,1430,1447,1464,1526,1543,1560,1568],{"type":14,"tag":37,"props":1343,"children":1344},{"class":675,"line":676},[1345,1350,1355,1359,1364],{"type":14,"tag":37,"props":1346,"children":1347},{"style":706},[1348],{"type":24,"value":1349},"gsap.",{"type":14,"tag":37,"props":1351,"children":1352},{"style":712},[1353],{"type":24,"value":1354},"to",{"type":14,"tag":37,"props":1356,"children":1357},{"style":706},[1358],{"type":24,"value":720},{"type":14,"tag":37,"props":1360,"children":1361},{"style":723},[1362],{"type":24,"value":1363},"'.gallery-track'",{"type":14,"tag":37,"props":1365,"children":1366},{"style":706},[1367],{"type":24,"value":759},{"type":14,"tag":37,"props":1369,"children":1370},{"class":675,"line":286},[1371,1376,1381,1385,1390,1395,1400],{"type":14,"tag":37,"props":1372,"children":1373},{"style":712},[1374],{"type":24,"value":1375},"  x",{"type":14,"tag":37,"props":1377,"children":1378},{"style":706},[1379],{"type":24,"value":1380},": () ",{"type":14,"tag":37,"props":1382,"children":1383},{"style":689},[1384],{"type":24,"value":866},{"type":14,"tag":37,"props":1386,"children":1387},{"style":689},[1388],{"type":24,"value":1389}," -",{"type":14,"tag":37,"props":1391,"children":1392},{"style":706},[1393],{"type":24,"value":1394},"(gallery.scrollWidth ",{"type":14,"tag":37,"props":1396,"children":1397},{"style":689},[1398],{"type":24,"value":1399},"-",{"type":14,"tag":37,"props":1401,"children":1402},{"style":706},[1403],{"type":24,"value":1404}," window.innerWidth),\n",{"type":14,"tag":37,"props":1406,"children":1407},{"class":675,"line":734},[1408,1413,1418],{"type":14,"tag":37,"props":1409,"children":1410},{"style":706},[1411],{"type":24,"value":1412},"  ease: ",{"type":14,"tag":37,"props":1414,"children":1415},{"style":723},[1416],{"type":24,"value":1417},"'none'",{"type":14,"tag":37,"props":1419,"children":1420},{"style":706},[1421],{"type":24,"value":778},{"type":14,"tag":37,"props":1423,"children":1424},{"class":675,"line":762},[1425],{"type":14,"tag":37,"props":1426,"children":1427},{"style":706},[1428],{"type":24,"value":1429},"  scrollTrigger: {\n",{"type":14,"tag":37,"props":1431,"children":1432},{"class":675,"line":781},[1433,1438,1443],{"type":14,"tag":37,"props":1434,"children":1435},{"style":706},[1436],{"type":24,"value":1437},"    trigger: ",{"type":14,"tag":37,"props":1439,"children":1440},{"style":723},[1441],{"type":24,"value":1442},"'.gallery-section'",{"type":14,"tag":37,"props":1444,"children":1445},{"style":706},[1446],{"type":24,"value":778},{"type":14,"tag":37,"props":1448,"children":1449},{"class":675,"line":799},[1450,1455,1460],{"type":14,"tag":37,"props":1451,"children":1452},{"style":706},[1453],{"type":24,"value":1454},"    start: ",{"type":14,"tag":37,"props":1456,"children":1457},{"style":723},[1458],{"type":24,"value":1459},"'top top'",{"type":14,"tag":37,"props":1461,"children":1462},{"style":706},[1463],{"type":24,"value":778},{"type":14,"tag":37,"props":1465,"children":1466},{"class":675,"line":816},[1467,1472,1476,1480,1485,1490,1495,1500,1504,1509,1513,1518,1522],{"type":14,"tag":37,"props":1468,"children":1469},{"style":712},[1470],{"type":24,"value":1471},"    end",{"type":14,"tag":37,"props":1473,"children":1474},{"style":706},[1475],{"type":24,"value":1380},{"type":14,"tag":37,"props":1477,"children":1478},{"style":689},[1479],{"type":24,"value":866},{"type":14,"tag":37,"props":1481,"children":1482},{"style":723},[1483],{"type":24,"value":1484}," `+=${",{"type":14,"tag":37,"props":1486,"children":1487},{"style":706},[1488],{"type":24,"value":1489},"gallery",{"type":14,"tag":37,"props":1491,"children":1492},{"style":723},[1493],{"type":24,"value":1494},".",{"type":14,"tag":37,"props":1496,"children":1497},{"style":706},[1498],{"type":24,"value":1499},"scrollWidth",{"type":14,"tag":37,"props":1501,"children":1502},{"style":689},[1503],{"type":24,"value":1389},{"type":14,"tag":37,"props":1505,"children":1506},{"style":706},[1507],{"type":24,"value":1508}," window",{"type":14,"tag":37,"props":1510,"children":1511},{"style":723},[1512],{"type":24,"value":1494},{"type":14,"tag":37,"props":1514,"children":1515},{"style":706},[1516],{"type":24,"value":1517},"innerWidth",{"type":14,"tag":37,"props":1519,"children":1520},{"style":723},[1521],{"type":24,"value":837},{"type":14,"tag":37,"props":1523,"children":1524},{"style":706},[1525],{"type":24,"value":778},{"type":14,"tag":37,"props":1527,"children":1528},{"class":675,"line":844},[1529,1534,1539],{"type":14,"tag":37,"props":1530,"children":1531},{"style":706},[1532],{"type":24,"value":1533},"    pin: ",{"type":14,"tag":37,"props":1535,"children":1536},{"style":695},[1537],{"type":24,"value":1538},"true",{"type":14,"tag":37,"props":1540,"children":1541},{"style":706},[1542],{"type":24,"value":778},{"type":14,"tag":37,"props":1544,"children":1545},{"class":675,"line":874},[1546,1551,1556],{"type":14,"tag":37,"props":1547,"children":1548},{"style":706},[1549],{"type":24,"value":1550},"    scrub: ",{"type":14,"tag":37,"props":1552,"children":1553},{"style":695},[1554],{"type":24,"value":1555},"1",{"type":14,"tag":37,"props":1557,"children":1558},{"style":706},[1559],{"type":24,"value":778},{"type":14,"tag":37,"props":1561,"children":1562},{"class":675,"line":893},[1563],{"type":14,"tag":37,"props":1564,"children":1565},{"style":706},[1566],{"type":24,"value":1567},"  },\n",{"type":14,"tag":37,"props":1569,"children":1570},{"class":675,"line":902},[1571],{"type":14,"tag":37,"props":1572,"children":1573},{"style":706},[1574],{"type":24,"value":1575},"})\n",{"type":14,"tag":1041,"props":1577,"children":1579},{"id":1578},"image-reveal-animation",[1580],{"type":24,"value":1581},"Image Reveal Animation",{"type":14,"tag":48,"props":1583,"children":1584},{},[1585],{"type":24,"value":1586},"ทุกรูปเข้าด้วย clip-path animation ที่ทำให้รู้สึกเหมือน \"เปิดม่าน\":",{"type":14,"tag":664,"props":1588,"children":1592},{"className":1589,"code":1590,"language":1591,"meta":8,"style":8},"language-css shiki shiki-themes github-light github-dark","@keyframes reveal {\n  from { clip-path: inset(0 100% 0 0); }\n  to   { clip-path: inset(0 0% 0 0); }\n}\n","css",[1593],{"type":14,"tag":388,"props":1594,"children":1595},{"__ignoreMap":8},[1596,1613,1674,1727],{"type":14,"tag":37,"props":1597,"children":1598},{"class":675,"line":676},[1599,1604,1609],{"type":14,"tag":37,"props":1600,"children":1601},{"style":689},[1602],{"type":24,"value":1603},"@keyframes",{"type":14,"tag":37,"props":1605,"children":1606},{"style":853},[1607],{"type":24,"value":1608}," reveal",{"type":14,"tag":37,"props":1610,"children":1611},{"style":706},[1612],{"type":24,"value":871},{"type":14,"tag":37,"props":1614,"children":1615},{"class":675,"line":286},[1616,1621,1626,1631,1636,1641,1645,1650,1655,1660,1665,1669],{"type":14,"tag":37,"props":1617,"children":1618},{"style":712},[1619],{"type":24,"value":1620},"  from",{"type":14,"tag":37,"props":1622,"children":1623},{"style":706},[1624],{"type":24,"value":1625}," { ",{"type":14,"tag":37,"props":1627,"children":1628},{"style":695},[1629],{"type":24,"value":1630},"clip-path",{"type":14,"tag":37,"props":1632,"children":1633},{"style":706},[1634],{"type":24,"value":1635},": ",{"type":14,"tag":37,"props":1637,"children":1638},{"style":695},[1639],{"type":24,"value":1640},"inset",{"type":14,"tag":37,"props":1642,"children":1643},{"style":706},[1644],{"type":24,"value":720},{"type":14,"tag":37,"props":1646,"children":1647},{"style":695},[1648],{"type":24,"value":1649},"0",{"type":14,"tag":37,"props":1651,"children":1652},{"style":695},[1653],{"type":24,"value":1654}," 100",{"type":14,"tag":37,"props":1656,"children":1657},{"style":689},[1658],{"type":24,"value":1659},"%",{"type":14,"tag":37,"props":1661,"children":1662},{"style":695},[1663],{"type":24,"value":1664}," 0",{"type":14,"tag":37,"props":1666,"children":1667},{"style":695},[1668],{"type":24,"value":1664},{"type":14,"tag":37,"props":1670,"children":1671},{"style":706},[1672],{"type":24,"value":1673},"); }\n",{"type":14,"tag":37,"props":1675,"children":1676},{"class":675,"line":734},[1677,1682,1687,1691,1695,1699,1703,1707,1711,1715,1719,1723],{"type":14,"tag":37,"props":1678,"children":1679},{"style":712},[1680],{"type":24,"value":1681},"  to",{"type":14,"tag":37,"props":1683,"children":1684},{"style":706},[1685],{"type":24,"value":1686},"   { ",{"type":14,"tag":37,"props":1688,"children":1689},{"style":695},[1690],{"type":24,"value":1630},{"type":14,"tag":37,"props":1692,"children":1693},{"style":706},[1694],{"type":24,"value":1635},{"type":14,"tag":37,"props":1696,"children":1697},{"style":695},[1698],{"type":24,"value":1640},{"type":14,"tag":37,"props":1700,"children":1701},{"style":706},[1702],{"type":24,"value":720},{"type":14,"tag":37,"props":1704,"children":1705},{"style":695},[1706],{"type":24,"value":1649},{"type":14,"tag":37,"props":1708,"children":1709},{"style":695},[1710],{"type":24,"value":1664},{"type":14,"tag":37,"props":1712,"children":1713},{"style":689},[1714],{"type":24,"value":1659},{"type":14,"tag":37,"props":1716,"children":1717},{"style":695},[1718],{"type":24,"value":1664},{"type":14,"tag":37,"props":1720,"children":1721},{"style":695},[1722],{"type":24,"value":1664},{"type":14,"tag":37,"props":1724,"children":1725},{"style":706},[1726],{"type":24,"value":1673},{"type":14,"tag":37,"props":1728,"children":1729},{"class":675,"line":762},[1730],{"type":14,"tag":37,"props":1731,"children":1732},{"style":706},[1733],{"type":24,"value":1734},"}\n",{"type":14,"tag":19,"props":1736,"children":1738},{"id":1737},"performance-optimization",[1739],{"type":24,"value":1740},"Performance Optimization",{"type":14,"tag":48,"props":1742,"children":1743},{},[1744],{"type":24,"value":1745},"ความท้าทายใหญ่คือรูปภาพคุณภาพสูงจาก photographer มืออาชีพที่ไฟล์ใหญ่มาก เราแก้ด้วย:",{"type":14,"tag":586,"props":1747,"children":1748},{},[1749,1759,1775,1785],{"type":14,"tag":590,"props":1750,"children":1751},{},[1752,1757],{"type":14,"tag":69,"props":1753,"children":1754},{},[1755],{"type":24,"value":1756},"@nuxt\u002Fimage",{"type":24,"value":1758}," + AVIF format ลดขนาดไฟล์ได้ 60-80%",{"type":14,"tag":590,"props":1760,"children":1761},{},[1762,1767,1769],{"type":14,"tag":69,"props":1763,"children":1764},{},[1765],{"type":24,"value":1766},"Intersection Observer",{"type":24,"value":1768}," สำหรับ lazy load ที่แม่นยำกว่า native ",{"type":14,"tag":388,"props":1770,"children":1772},{"className":1771},[],[1773],{"type":24,"value":1774},"loading=\"lazy\"",{"type":14,"tag":590,"props":1776,"children":1777},{},[1778,1783],{"type":14,"tag":69,"props":1779,"children":1780},{},[1781],{"type":24,"value":1782},"Blur placeholder",{"type":24,"value":1784}," เพื่อให้หน้าดู complete ระหว่างรูปโหลด",{"type":14,"tag":590,"props":1786,"children":1787},{},[1788,1793],{"type":14,"tag":69,"props":1789,"children":1790},{},[1791],{"type":24,"value":1792},"Priority load",{"type":24,"value":1794}," เฉพาะรูป above-the-fold",{"type":14,"tag":19,"props":1796,"children":1797},{"id":127},[1798],{"type":24,"value":127},{"type":14,"tag":131,"props":1800,"children":1801},{},[1802,1821],{"type":14,"tag":135,"props":1803,"children":1804},{},[1805],{"type":14,"tag":139,"props":1806,"children":1807},{},[1808,1812,1816],{"type":14,"tag":143,"props":1809,"children":1810},{},[1811],{"type":24,"value":147},{"type":14,"tag":143,"props":1813,"children":1814},{},[1815],{"type":24,"value":152},{"type":14,"tag":143,"props":1817,"children":1818},{},[1819],{"type":24,"value":1820},"หลัง",{"type":14,"tag":159,"props":1822,"children":1823},{},[1824,1840,1858,1876,1894],{"type":14,"tag":139,"props":1825,"children":1826},{},[1827,1831,1836],{"type":14,"tag":166,"props":1828,"children":1829},{},[1830],{"type":24,"value":224},{"type":14,"tag":166,"props":1832,"children":1833},{},[1834],{"type":24,"value":1835},"52",{"type":14,"tag":166,"props":1837,"children":1838},{},[1839],{"type":24,"value":977},{"type":14,"tag":139,"props":1841,"children":1842},{},[1843,1848,1853],{"type":14,"tag":166,"props":1844,"children":1845},{},[1846],{"type":24,"value":1847},"LCP",{"type":14,"tag":166,"props":1849,"children":1850},{},[1851],{"type":24,"value":1852},"4.8s",{"type":14,"tag":166,"props":1854,"children":1855},{},[1856],{"type":24,"value":1857},"0.9s",{"type":14,"tag":139,"props":1859,"children":1860},{},[1861,1866,1871],{"type":14,"tag":166,"props":1862,"children":1863},{},[1864],{"type":24,"value":1865},"Bounce rate",{"type":14,"tag":166,"props":1867,"children":1868},{},[1869],{"type":24,"value":1870},"71%",{"type":14,"tag":166,"props":1872,"children":1873},{},[1874],{"type":24,"value":1875},"38%",{"type":14,"tag":139,"props":1877,"children":1878},{},[1879,1884,1889],{"type":14,"tag":166,"props":1880,"children":1881},{},[1882],{"type":24,"value":1883},"Average time on site",{"type":14,"tag":166,"props":1885,"children":1886},{},[1887],{"type":24,"value":1888},"1:20",{"type":14,"tag":166,"props":1890,"children":1891},{},[1892],{"type":24,"value":1893},"4:45",{"type":14,"tag":139,"props":1895,"children":1896},{},[1897,1902,1907],{"type":14,"tag":166,"props":1898,"children":1899},{},[1900],{"type":24,"value":1901},"Lead form submissions",{"type":14,"tag":166,"props":1903,"children":1904},{},[1905],{"type":24,"value":1906},"3\u002Fเดือน",{"type":14,"tag":166,"props":1908,"children":1909},{},[1910],{"type":24,"value":1911},"12\u002Fเดือน",{"type":14,"tag":254,"props":1913,"children":1914},{},[1915,1920],{"type":14,"tag":48,"props":1916,"children":1917},{},[1918],{"type":24,"value":1919},"\"ลูกค้าคนแรกที่ติดต่อมาหลังเว็บใหม่ออนไลน์ บอกว่าตัดสินใจจากเว็บเลย ไม่ได้ดู portfolio เพิ่มเติม\"",{"type":14,"tag":48,"props":1921,"children":1922},{},[1923],{"type":24,"value":1924},"— คุณวรรณา สุนทร, Creative Director",{"type":14,"tag":992,"props":1926,"children":1927},{},[1928],{"type":24,"value":996},{"title":8,"searchDepth":286,"depth":286,"links":1930},[1931,1932,1937,1938],{"id":1285,"depth":286,"text":1288},{"id":1296,"depth":286,"text":1296,"children":1933},[1934,1935,1936],{"id":1301,"depth":734,"text":1304},{"id":1312,"depth":734,"text":1315},{"id":1578,"depth":734,"text":1581},{"id":1737,"depth":286,"text":1740},{"id":127,"depth":286,"text":127},"content:portfolio:baan-design-studio.md","portfolio\u002Fbaan-design-studio.md","portfolio\u002Fbaan-design-studio",{"_path":1943,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":1944,"description":8,"category":304,"client":1945,"timeline":1946,"role":1947,"date":1948,"tags":1949,"body":1955,"_type":295,"_id":2150,"_source":297,"_file":2151,"_stem":2152,"_extension":300},"\u002Fportfolio\u002Forchard-saas","Orchard — HR & Payroll SaaS สำหรับ SME ไทย","Orchard HR Tech","20 สัปดาห์","Full-stack Architecture, Frontend, Backend API","2023-11-15",[1950,1951,312,1019,555,1952,1953,1954],"React","Next.js","Redis","Docker","AWS ECS",{"type":11,"children":1956,"toc":2140},[1957,1962,1967,1973,1979,1991,2009,2015,2027,2033,2038,2061,2066,2078,2089,2093],{"type":14,"tag":19,"props":1958,"children":1960},{"id":1959},"ภาพรวม",[1961],{"type":24,"value":1959},{"type":14,"tag":48,"props":1963,"children":1964},{},[1965],{"type":24,"value":1966},"Orchard เป็นโปรเจกต์ที่ใหญ่และซับซ้อนที่สุดที่เราเคยทำ — HR SaaS สำหรับบริษัทขนาด 20-500 คน ที่ต้องรองรับทั้ง time attendance, leave management, payroll calculation และ compliance กับกฎหมายแรงงานไทย",{"type":14,"tag":19,"props":1968,"children":1970},{"id":1969},"ความท้าทายด้าน-architecture",[1971],{"type":24,"value":1972},"ความท้าทายด้าน Architecture",{"type":14,"tag":1041,"props":1974,"children":1976},{"id":1975},"multi-tenant-data-isolation",[1977],{"type":24,"value":1978},"Multi-tenant Data Isolation",{"type":14,"tag":48,"props":1980,"children":1981},{},[1982,1984,1989],{"type":24,"value":1983},"เราออกแบบ ",{"type":14,"tag":69,"props":1985,"children":1986},{},[1987],{"type":24,"value":1988},"schema-per-tenant",{"type":24,"value":1990}," บน PostgreSQL แทน row-level isolation เพราะ:",{"type":14,"tag":586,"props":1992,"children":1993},{},[1994,1999,2004],{"type":14,"tag":590,"props":1995,"children":1996},{},[1997],{"type":24,"value":1998},"Query performance ดีกว่าเมื่อ dataset ใหญ่ขึ้น",{"type":14,"tag":590,"props":2000,"children":2001},{},[2002],{"type":24,"value":2003},"Database maintenance ทำได้ง่ายกว่า",{"type":14,"tag":590,"props":2005,"children":2006},{},[2007],{"type":24,"value":2008},"สามารถ provision ทรัพยากรแยกกันได้สำหรับ enterprise tier",{"type":14,"tag":1041,"props":2010,"children":2012},{"id":2011},"payroll-calculation-engine",[2013],{"type":24,"value":2014},"Payroll Calculation Engine",{"type":14,"tag":48,"props":2016,"children":2017},{},[2018,2020,2025],{"type":24,"value":2019},"กฎหมายแรงงานไทยมีความซับซ้อนสูง ทั้งการคำนวณ OT, วันหยุดชดเชย, ประกันสังคม, ภาษี ณ ที่จ่าย เราออกแบบเป็น ",{"type":14,"tag":69,"props":2021,"children":2022},{},[2023],{"type":24,"value":2024},"rule engine",{"type":24,"value":2026}," ที่อ่าน business rules จาก configuration แทนการ hardcode เพื่อรองรับการเปลี่ยนแปลงกฎหมายในอนาคต",{"type":14,"tag":1041,"props":2028,"children":2030},{"id":2029},"performance-ของ-report-generation",[2031],{"type":24,"value":2032},"Performance ของ Report Generation",{"type":14,"tag":48,"props":2034,"children":2035},{},[2036],{"type":24,"value":2037},"รายงาน payroll บริษัท 300 คนต้องสร้างใน \u003C 3 วินาที เราแก้ด้วยการ:",{"type":14,"tag":1094,"props":2039,"children":2040},{},[2041,2051,2056],{"type":14,"tag":590,"props":2042,"children":2043},{},[2044,2046],{"type":24,"value":2045},"Pre-compute ข้อมูลสรุปทุกสิ้นเดือนเก็บไว้ใน ",{"type":14,"tag":69,"props":2047,"children":2048},{},[2049],{"type":24,"value":2050},"Redis cache",{"type":14,"tag":590,"props":2052,"children":2053},{},[2054],{"type":24,"value":2055},"Generate PDF แบบ async บน background job",{"type":14,"tag":590,"props":2057,"children":2058},{},[2059],{"type":24,"value":2060},"ส่ง email + notification เมื่อ PDF พร้อม",{"type":14,"tag":19,"props":2062,"children":2064},{"id":2063},"สิ่งที่ทำให้โปรเจกต์นี้พิเศษ",[2065],{"type":24,"value":2063},{"type":14,"tag":48,"props":2067,"children":2068},{},[2069,2071,2076],{"type":24,"value":2070},"การทำ ",{"type":14,"tag":69,"props":2072,"children":2073},{},[2074],{"type":24,"value":2075},"audit trail",{"type":24,"value":2077}," ที่สมบูรณ์สำหรับทุก payroll transaction — ทุกการเปลี่ยนแปลงต้องบันทึกว่าใคร เปลี่ยนอะไร เมื่อไหร่ และทำไม เพราะ HR data มี legal implication ที่สูงมาก",{"type":14,"tag":48,"props":2079,"children":2080},{},[2081,2082,2087],{"type":24,"value":648},{"type":14,"tag":69,"props":2083,"children":2084},{},[2085],{"type":24,"value":2086},"event sourcing pattern",{"type":24,"value":2088}," บน PostgreSQL (ไม่ใช่ event database พิเศษ) ซึ่งง่ายกว่าแต่ให้ auditability ที่เพียงพอ",{"type":14,"tag":19,"props":2090,"children":2091},{"id":127},[2092],{"type":24,"value":127},{"type":14,"tag":586,"props":2094,"children":2095},{},[2096,2106,2118,2130],{"type":14,"tag":590,"props":2097,"children":2098},{},[2099,2101],{"type":24,"value":2100},"ลูกค้า pilot 8 บริษัท เวลาทำ payroll เฉลี่ยลดจาก ",{"type":14,"tag":69,"props":2102,"children":2103},{},[2104],{"type":24,"value":2105},"6 ชั่วโมง → 45 นาที",{"type":14,"tag":590,"props":2107,"children":2108},{},[2109,2111,2116],{"type":24,"value":2110},"Error ใน payroll calculation ลดลง ",{"type":14,"tag":69,"props":2112,"children":2113},{},[2114],{"type":24,"value":2115},"100%",{"type":24,"value":2117}," (จาก manual Excel)",{"type":14,"tag":590,"props":2119,"children":2120},{},[2121,2123,2128],{"type":24,"value":2122},"Net Promoter Score: ",{"type":14,"tag":69,"props":2124,"children":2125},{},[2126],{"type":24,"value":2127},"72",{"type":24,"value":2129}," (ระดับ excellent)",{"type":14,"tag":590,"props":2131,"children":2132},{},[2133,2135],{"type":24,"value":2134},"Uptime 12 เดือนหลัง launch: ",{"type":14,"tag":69,"props":2136,"children":2137},{},[2138],{"type":24,"value":2139},"99.94%",{"title":8,"searchDepth":286,"depth":286,"links":2141},[2142,2143,2148,2149],{"id":1959,"depth":286,"text":1959},{"id":1969,"depth":286,"text":1972,"children":2144},[2145,2146,2147],{"id":1975,"depth":734,"text":1978},{"id":2011,"depth":734,"text":2014},{"id":2029,"depth":734,"text":2032},{"id":2063,"depth":286,"text":2063},{"id":127,"depth":286,"text":127},"content:portfolio:orchard-saas.md","portfolio\u002Forchard-saas.md","portfolio\u002Forchard-saas",{"_path":2154,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":2155,"description":8,"category":1009,"client":2156,"timeline":2157,"role":2158,"date":2159,"tags":2160,"body":2162,"_type":295,"_id":2312,"_source":297,"_file":2313,"_stem":2314,"_extension":300},"\u002Fportfolio\u002Fbitebuddy-food-app","BiteBuddy — แอปค้นหาร้านอาหารและรีวิวเพื่อชุมชน","BiteBuddy Co., Ltd.","16 สัปดาห์","Mobile Development, AI Integration, UX Research","2023-08-01",[1015,1017,1018,2161,1019,312,1952],"OpenAI API",{"type":11,"children":2163,"toc":2302},[2164,2169,2174,2180,2186,2198,2210,2216,2221,2227,2232,2238,2256,2260],{"type":14,"tag":19,"props":2165,"children":2167},{"id":2166},"ที่มาของไอเดีย",[2168],{"type":24,"value":2166},{"type":14,"tag":48,"props":2170,"children":2171},{},[2172],{"type":24,"value":2173},"ลูกค้ามาหาเราพร้อมกับปัญหาที่คนไทยหลายล้านคนเจอทุกวัน: \"จะกินอะไรดี?\" — แต่วิธีแก้ปัญหาที่มีอยู่ (Google Maps, Wongnai) ให้ผลลัพธ์ที่กว้างเกินไป ไม่ contextual และไม่รู้สึกเป็น \"เพื่อนแนะนำ\"",{"type":14,"tag":19,"props":2175,"children":2177},{"id":2176},"features-ที่น่าสนใจ",[2178],{"type":24,"value":2179},"Features ที่น่าสนใจ",{"type":14,"tag":1041,"props":2181,"children":2183},{"id":2182},"วันนี้กินอะไรดี-ai-recommendation",[2184],{"type":24,"value":2185},"\"วันนี้กินอะไรดี?\" AI Recommendation",{"type":14,"tag":48,"props":2187,"children":2188},{},[2189,2191,2196],{"type":24,"value":2190},"ระบบถามเพียง 3 คำถามสั้น (อารมณ์, งบ, คนกี่คน) แล้วแนะนำร้านที่ match ด้วย ",{"type":14,"tag":69,"props":2192,"children":2193},{},[2194],{"type":24,"value":2195},"OpenAI GPT-4o-mini",{"type":24,"value":2197}," ที่มี context ของร้านในฐานข้อมูล",{"type":14,"tag":48,"props":2199,"children":2200},{},[2201,2203,2208],{"type":24,"value":2202},"เราออกแบบให้ AI response เป็น ",{"type":14,"tag":69,"props":2204,"children":2205},{},[2206],{"type":24,"value":2207},"structured JSON",{"type":24,"value":2209}," เสมอ เพื่อให้ parse ง่ายและ fallback ได้เมื่อ AI ตอบผิด format",{"type":14,"tag":1041,"props":2211,"children":2213},{"id":2212},"hyperlocal-review-system",[2214],{"type":24,"value":2215},"Hyperlocal Review System",{"type":14,"tag":48,"props":2217,"children":2218},{},[2219],{"type":24,"value":2220},"รีวิวแบ่งตาม \"context\" — รีวิวสำหรับกินคนเดียว vs มาเป็นครอบครัว vs business lunch ให้ผู้ใช้เลือก context ที่ตรงกับตัวเองก่อนอ่านรีวิว",{"type":14,"tag":1041,"props":2222,"children":2224},{"id":2223},"real-time-คนกำลังกินอยู่",[2225],{"type":24,"value":2226},"Real-time \"คนกำลังกินอยู่\"",{"type":14,"tag":48,"props":2228,"children":2229},{},[2230],{"type":24,"value":2231},"แสดงจำนวน active user ที่ check-in ร้านนั้น ณ ขณะนั้น ทำให้รู้ว่าร้านคึกคักไหม โดยไม่ต้องโทรถาม",{"type":14,"tag":19,"props":2233,"children":2235},{"id":2234},"architecture-decision-firebase-vs-custom-backend",[2236],{"type":24,"value":2237},"Architecture Decision: Firebase vs Custom Backend",{"type":14,"tag":48,"props":2239,"children":2240},{},[2241,2243,2247,2249,2254],{"type":24,"value":2242},"เราตัดสินใจใช้ ",{"type":14,"tag":69,"props":2244,"children":2245},{},[2246],{"type":24,"value":1017},{"type":24,"value":2248}," สำหรับ auth, realtime features และ push notifications แต่ ",{"type":14,"tag":69,"props":2250,"children":2251},{},[2252],{"type":24,"value":2253},"Node.js custom API",{"type":24,"value":2255}," สำหรับ restaurant data และ AI calls เพราะ Firebase query ไม่ flexible พอสำหรับ geo-based search ที่ซับซ้อน",{"type":14,"tag":19,"props":2257,"children":2258},{"id":127},[2259],{"type":24,"value":127},{"type":14,"tag":586,"props":2261,"children":2262},{},[2263,2268,2285,2290],{"type":14,"tag":590,"props":2264,"children":2265},{},[2266],{"type":24,"value":2267},"28,000+ downloads ใน 4 เดือนแรก (organic, no paid ads)",{"type":14,"tag":590,"props":2269,"children":2270},{},[2271,2273,2278,2280],{"type":24,"value":2272},"App Store Rating: ",{"type":14,"tag":69,"props":2274,"children":2275},{},[2276],{"type":24,"value":2277},"4.5 ⭐",{"type":24,"value":2279}," Play Store: ",{"type":14,"tag":69,"props":2281,"children":2282},{},[2283],{"type":24,"value":2284},"4.4 ⭐",{"type":14,"tag":590,"props":2286,"children":2287},{},[2288],{"type":24,"value":2289},"Daily Active Users: 4,800 (17% DAU\u002FMAU ratio)",{"type":14,"tag":590,"props":2291,"children":2292},{},[2293,2295,2300],{"type":24,"value":2294},"AI recommendation feature ถูกใช้ ",{"type":14,"tag":69,"props":2296,"children":2297},{},[2298],{"type":24,"value":2299},"43%",{"type":24,"value":2301}," ของ sessions",{"title":8,"searchDepth":286,"depth":286,"links":2303},[2304,2305,2310,2311],{"id":2166,"depth":286,"text":2166},{"id":2176,"depth":286,"text":2179,"children":2306},[2307,2308,2309],{"id":2182,"depth":734,"text":2185},{"id":2212,"depth":734,"text":2215},{"id":2223,"depth":734,"text":2226},{"id":2234,"depth":286,"text":2237},{"id":127,"depth":286,"text":127},"content:portfolio:bitebuddy-food-app.md","portfolio\u002Fbitebuddy-food-app.md","portfolio\u002Fbitebuddy-food-app",1780350561143]