{"id":836,"date":"2025-11-07T20:00:41","date_gmt":"2025-11-07T20:00:41","guid":{"rendered":"https:\/\/yarima.org\/?page_id=836"},"modified":"2025-11-17T07:27:22","modified_gmt":"2025-11-17T07:27:22","slug":"nutrients-finder","status":"publish","type":"page","link":"https:\/\/yarima.org\/?page_id=836","title":{"rendered":"Nutrients Finder"},"content":{"rendered":"\n<!-- ---------- Yarima Nutrition Finder v3 (Autocomplete tiles + Gradient Bars) ---------- -->\n<div id=\"nutrition-finder\" style=\"font-family:'Open Sans',sans-serif; max-width:980px; margin:20px auto; padding:18px; background:#f8faf8; border-radius:12px; box-shadow:0 6px 18px rgba(0,0,0,0.06);\">\n  <h2 style=\"color:#366f32; text-align:center; margin:0 0 8px;\">Nutrition Finder \ud83e\udd57<\/h2>\n  <p style=\"text-align:center; font-size:13px; color:#666; margin:0 0 14px;\">Powered by USDA \u2013 Search any food to learn about its nutrients.<\/p>\n\n  <!-- Search row -->\n  <div style=\"display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:12px;\">\n    <div style=\"flex:1; position:relative;\">\n      <input id=\"foodInput\" type=\"text\" placeholder=\"Type a food name...\" autocomplete=\"off\"\n        style=\"width:100%; padding:11px 14px; border-radius:8px; border:1px solid #dcdcdc; font-size:16px; box-sizing:border-box;\">\n      <div id=\"suggestions\" style=\"position:relative; top:54px; left:0; right:0; z-index:90;\"><\/div>\n    <\/div>\n\n    <input id=\"servingInput\" type=\"number\" min=\"1\" placeholder=\"Serving (g) - default 100g\"\n      style=\"width:170px; padding:10px; border-radius:8px; border:1px solid #dcdcdc; font-size:15px;\">\n    <button id=\"searchBtn\" disabled\n      style=\"padding:10px 18px; background:#366f32; color:white; border:none; border-radius:8px; cursor:pointer; font-weight:600;\">\n      Show \/ Refresh\n    <\/button>\n  <\/div>\n\n  <!-- Selected food row -->\n  <div id=\"selectedInfo\" style=\"display:none; align-items:center; gap:10px; margin-bottom:12px;\"><\/div>\n\n  <!-- Top nutrients (cards) -->\n  <div id=\"topNutrients\" style=\"display:flex; flex-wrap:wrap; gap:14px; margin-bottom:18px;\"><\/div>\n\n  <!-- All nutrients -->\n  <div id=\"results\" style=\"display:flex; flex-wrap:wrap; gap:12px;\"><\/div>\n\n  <!-- small footer note -->\n  <div style=\"font-size:12px; color:#777; margin-top:12px;\">Tip: Use the serving input to scale values (default uses the food&#8217;s serving or 100g).<\/div>\n<\/div>\n\n<style>\n  \/* ---------- Styles (gradient bars) ---------- *\/\n  .tiles { display:flex; flex-wrap:wrap; gap:10px; padding:6px 0 0 0; }\n  .suggestion-card{\n    display:flex; gap:10px; align-items:center;\n    background:#fff; border-radius:10px; padding:8px;\n    min-width:220px; max-width:260px; box-shadow:0 2px 8px rgba(0,0,0,0.06);\n    cursor:pointer; border-left:6px solid #366f32;\n  }\n  .suggestion-card img{ width:64px; height:64px; object-fit:cover; border-radius:8px; background:#f6f6f6; }\n  .suggestion-card .meta{ font-size:13px; color:#333; }\n  .suggestion-card .meta .title{ font-weight:700; color:#366f32; margin-bottom:4px; }\n  .suggestion-card .meta .sub{ color:#777; font-size:12px; }\n\n  .spinner { width:18px; height:18px; border-radius:50%; border:3px solid rgba(54,111,50,0.18); border-top-color:#366f32; animation:spin 1s linear infinite; display:inline-block; vertical-align:middle;}\n  @keyframes spin { to { transform:rotate(360deg); } }\n\n  .nutri-card {\n    flex:1 1 220px; background:#fff; border-radius:10px; padding:12px; box-shadow:0 2px 10px rgba(0,0,0,0.05);\n    border-left:6px solid #366f32;\n  }\n  .nutri-card h4 { margin:0; font-size:14px; color:#2b5a2d; display:flex; align-items:center; gap:8px; }\n  .nutri-value { color:#444; font-size:13px; margin-top:6px; }\n  .gradient-bar {\n    margin-top:8px; height:10px; border-radius:10px; background:linear-gradient(90deg,#e8f2e8,#f7f9f7); overflow:hidden;\n  }\n  .gradient-bar-inner {\n    height:100%; width:0%; border-radius:10px; transition: width .7s ease;\n    \/* gradient fill for inner *\/\n    background: linear-gradient(90deg, #f9d342 0%, #f17f2b 40%, #d63a3a 100%);\n  }\n\n  \/* responsive *\/\n  @media (max-width:880px){\n    .suggestion-card { min-width:45%; }\n    .nutri-card { flex:1 1 100%; }\n  }\n<\/style>\n\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n  const apiKey = \"kzWMHUwCc8TeJ4hpbmRxaog3XWpWerAjSxm7XWsP\";\n  const minChars = 2;\n  const suggestionLimit = 5;\n  const searchInput = document.getElementById('foodInput');\n  const servingInput = document.getElementById('servingInput');\n  const suggestionsContainer = document.getElementById('suggestions');\n  const resultsDiv = document.getElementById('results');\n  const topDiv = document.getElementById('topNutrients');\n  const searchBtn = document.getElementById('searchBtn');\n  const selectedInfo = document.getElementById('selectedInfo');\n\n  \/\/ maps & references for gradient scaling (for percent bars)\n  const dailyReference = {\n    \"energy\": 2000,\n    \"protein\": 50,\n    \"total lipid\": 70,\n    \"carbohydrate\": 275,\n    \"fiber\": 28,\n    \"sugars\": 50,\n    \"iron\": 18,\n    \"calcium\": 1000,\n    \"vitamin c\": 90,\n    \"potassium\": 4700\n  };\n\n  const nutrientIcons = {\n    \"energy\": \"\u26a1\", \"protein\":\"\ud83e\udd69\", \"carbohydrate\":\"\ud83c\udf5e\", \"total lipid\":\"\ud83e\udd51\", \"fiber\":\"\ud83c\udf3e\",\n    \"sugars\":\"\ud83c\udf6c\", \"vitamin c\":\"\ud83c\udf4a\", \"iron\":\"\ud83e\ude78\", \"calcium\":\"\ud83e\uddb4\",\"potassium\":\"\ud83e\udec0\"\n  };\n\n  let debounceTimer = null;\n  let selectedFood = null;\n  let lastQuery = \"\";\n\n  function escapeHtml(str){ return String(str || '').replace(\/[&<>\"']\/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[m])); }\n\n  function clearSuggestions(){ suggestionsContainer.innerHTML = \"\"; }\n  function showMessageInSuggestions(msg){ suggestionsContainer.innerHTML = '<div style=\"padding:8px;color:#666;\">'+msg+'<\/div>'; }\n\n  function getPlaceholderSVG(letter){\n    const bg = '#f6f6f6';\n    const color = '#366f32';\n    const svg = `<svg xmlns='http:\/\/www.w3.org\/2000\/svg' width='200' height='200'><rect width='100%' height='100%' fill='${bg}'\/><text x='50%' y='55%' font-size='72' text-anchor='middle' fill='${color}' font-family='Arial, Helvetica, sans-serif'>${letter}<\/text><\/svg>`;\n    return `data:image\/svg+xml;utf8,${encodeURIComponent(svg)}`;\n  }\n\n  \/\/ Extract energy if present\n  function extractCalories(nutrients){\n    const e = (nutrients || []).find(n => \/energy\/i.test(n.nutrientName) || \/calori\/i.test(n.nutrientName));\n    if(!e) return null;\n    return { value: e.value ?? e.amount ?? 0, unit: e.unitName || e.unit || 'kcal' };\n  }\n\n  async function fetchSuggestions(query){\n    clearSuggestions();\n    if(!query || query.length < minChars) return;\n    suggestionsContainer.innerHTML = '<div style=\"padding:8px;\"><span class=\"spinner\"><\/span> Searching\u2026<\/div>';\n    try {\n      const res = await fetch(`https:\/\/api.nal.usda.gov\/fdc\/v1\/foods\/search?query=${encodeURIComponent(query)}&pageSize=25&dataType=SR%20Legacy,Foundation,Survey%20(FNDDS)&api_key=${apiKey}`);\n      const data = await res.json();\n      if(!data || !Array.isArray(data.foods) || data.foods.length === 0) {\n        showMessageInSuggestions(\"No matching foods found.\");\n        return;\n      }\n\n      \/\/ Keep non-branded & sensible descriptions\n      const filtered = data.foods\n        .filter(f => !((f.dataType||'').toLowerCase().includes('branded')))\n        .filter(f => f.description && \/[a-zA-Z]\/.test(f.description))\n        .filter((f,idx,arr) => arr.findIndex(x=>x.description.toLowerCase()===f.description.toLowerCase())===idx);\n\n      if(filtered.length === 0){ showMessageInSuggestions(\"No generic matches found. Try another word.\"); return; }\n\n      const top = filtered.slice(0, suggestionLimit);\n      const tiles = document.createElement('div');\n      tiles.className = 'tiles';\n      top.forEach(item => {\n        const cal = item.foodNutrients && item.foodNutrients.length ? extractCalories(item.foodNutrients) : null;\n        const card = document.createElement('div');\n        card.className = 'suggestion-card';\n        card.setAttribute('data-fdc', item.fdcId);\n        card.setAttribute('data-desc', item.description);\n        card.innerHTML = `\n          <img decoding=\"async\" src=\"${getPlaceholderSVG((item.description||'F').charAt(0).toUpperCase())}\" alt=\"${escapeHtml(item.description)}\" \/>\n          <div class=\"meta\">\n            <div class=\"title\">${escapeHtml(item.description)}<\/div>\n            <div class=\"sub\">${cal ? (cal.value + ' ' + (cal.unit||'kcal')) : (escapeHtml(item.foodCategory || 'Food'))}<\/div>\n          <\/div>\n        `;\n        card.addEventListener('click', () => selectFoodTile(item));\n        tiles.appendChild(card);\n      });\n\n      suggestionsContainer.innerHTML = \"\";\n      suggestionsContainer.appendChild(tiles);\n    } catch (err) {\n      console.error(\"USDA search error:\", err);\n      showMessageInSuggestions(\"Error searching USDA. Try again later.\");\n    }\n  }\n\n  \/\/ select a food -> show selected info and auto-load nutrition\n  function selectFoodTile(item){\n    selectedFood = item;\n    suggestionsContainer.innerHTML = \"\";\n    \/\/ update selected info bar\n    selectedInfo.style.display = 'flex';\n    selectedInfo.style.alignItems = 'center';\n    selectedInfo.style.justifyContent = 'space-between';\n    selectedInfo.style.background = '#fff';\n    selectedInfo.style.padding = '10px';\n    selectedInfo.style.borderRadius = '8px';\n    selectedInfo.style.borderLeft = '6px solid #366f32';\n    selectedInfo.innerHTML = `\n      <div style=\"display:flex; gap:12px; align-items:center;\">\n        <div style=\"width:44px; height:44px; border-radius:8px; background:#f6f6f6; display:flex; align-items:center; justify-content:center; font-weight:700; color:#366f32;\">\n          ${escapeHtml((item.description||'').charAt(0).toUpperCase())}\n        <\/div>\n        <div>\n          <div style=\"font-weight:700; color:#366f32;\">${escapeHtml(item.description)}<\/div>\n          <div style=\"font-size:13px; color:#666;\">Selected \u2014 loading nutrients...<\/div>\n        <\/div>\n      <\/div>\n      <div style=\"display:flex; gap:8px; align-items:center;\">\n        <button id=\"clearSelection\" style=\"padding:6px 10px; border-radius:6px; border:1px solid #ccc; background:#fff; cursor:pointer;\">Change<\/button>\n      <\/div>\n    `;\n    document.getElementById('clearSelection').addEventListener('click', () => {\n      selectedFood = null;\n      selectedInfo.style.display = 'none';\n      document.getElementById('foodInput').focus();\n      resultsDiv.innerHTML = \"\";\n      topDiv.innerHTML = \"\";\n      searchBtn.disabled = true;\n    });\n\n    \/\/ enable button and auto load nutrients\n    searchBtn.disabled = false;\n    fetchNutritionForSelected(); \/\/ auto-fetch on selection\n  }\n\n  \/\/ Debounce user input\n  searchInput.addEventListener('input', (e) => {\n    const q = e.target.value.trim();\n    \/\/ reset state if user edits\n    selectedFood = null;\n    selectedInfo.style.display = 'none';\n    searchBtn.disabled = true;\n    resultsDiv.innerHTML = \"\";\n    topDiv.innerHTML = \"\";\n\n    if(debounceTimer) clearTimeout(debounceTimer);\n    if(!q || q.length < minChars){ clearSuggestions(); return; }\n    debounceTimer = setTimeout(() => {\n      if(q === lastQuery) return;\n      lastQuery = q;\n      fetchSuggestions(q);\n    }, 350);\n  });\n\n  \/\/ Button manual refresh (optional)\n  searchBtn.addEventListener('click', async () => {\n    if(!selectedFood) return alert(\"Please select a food from suggestions first.\");\n    \/\/ allow manual refresh which re-fetches and redraws with serving scaling\n    await fetchNutritionForSelected();\n  });\n\n  \/\/ fetch nutrient details robustly\n  async function fetchNutritionForSelected() {\n    if(!selectedFood || !selectedFood.fdcId) {\n      alert(\"Select a food first.\");\n      return;\n    }\n    \/\/ loading UI\n    resultsDiv.innerHTML = `<div style=\"padding:12px;\"><span class=\"spinner\"><\/span> Loading nutrients\u2026<\/div>`;\n    topDiv.innerHTML = \"\";\n\n    try {\n      const fdcId = selectedFood.fdcId;\n      const res = await fetch(`https:\/\/api.nal.usda.gov\/fdc\/v1\/food\/${encodeURIComponent(fdcId)}?api_key=${apiKey}`);\n      if(!res.ok){ throw new Error('USDA API: ' + res.status); }\n      const data = await res.json();\n\n      \/\/ find nutrient array robustly\n      let nutrients = data.foodNutrients || data.foodNutrient || [];\n      if(!Array.isArray(nutrients) && data.labelNutrients && typeof data.labelNutrients === 'object'){\n        nutrients = Object.entries(data.labelNutrients).map(([k,v]) => ({ nutrientName:k, value:(v && (v.value||v.amount))||0, unitName:v && (v.unitName||v.unit) || '' }));\n      }\n      \/\/ fallback nested\n      if(!Array.isArray(nutrients) && Array.isArray(data.foods) && data.foods.length) {\n        nutrients = data.foods[0].foodNutrients || [];\n      }\n      if(!Array.isArray(nutrients)) nutrients = [];\n\n      \/\/ base serving (if available) else 100g\n      const baseServing = data.servingSize || 100;\n      const servingUnit = data.servingSizeUnit || 'g';\n      const customServing = parseFloat(servingInput.value);\n      const actualServing = (customServing && customServing > 0) ? customServing : baseServing;\n      const scaleFactor = actualServing \/ baseServing;\n\n      \/\/ normalize nutrient objects: map value -> amount if present\n      nutrients = nutrients.map(n => {\n        const name = n.nutrientName || n.name || (n.nutrient && n.nutrient.name) || '';\n        const unit = n.unitName || n.unit || (n.nutrient && n.nutrient.unitName) || '';\n        const rawVal = (n.amount ?? n.value ?? n['nutrient']?.amount ?? n['nutrient']?.value);\n        return { nutrientName: name, unitName: unit, amount: (rawVal === undefined || rawVal === null) ? NaN : (Number(rawVal) * scaleFactor) };\n      }).filter(n => n.nutrientName && !isNaN(n.amount));\n\n      if(nutrients.length === 0){\n        resultsDiv.innerHTML = \"<div style='padding:12px;color:#666;'>No nutrient data available for this item.<\/div>\";\n        return;\n      }\n\n      \/\/ sort so energy\/macros appear first\n      const priority = [\"energy\",\"protein\",\"carbohydrate\",\"total lipid\",\"fat\",\"fiber\",\"sugars\",\"sodium\"];\n      nutrients.sort((a,b) => {\n        const an = a.nutrientName.toLowerCase(), bn = b.nutrientName.toLowerCase();\n        const ai = priority.findIndex(p=>an.includes(p));\n        const bi = priority.findIndex(p=>bn.includes(p));\n        if(ai === -1 && bi === -1) return b.amount - a.amount;\n        if(ai === -1) return 1;\n        if(bi === -1) return -1;\n        return ai - bi;\n      });\n\n      \/\/ display top 5 as cards and all as list\n      const topFive = nutrients.slice(0,5);\n      const rest = nutrients.slice(5);\n\n      \/\/ build top cards\n        \n\n      \/\/ build full list (rest + remaining top-five if you want)\n      resultsDiv.innerHTML = \"\";\n      const listContainer = document.createElement('div');\n      listContainer.style.display = 'flex';\n      listContainer.style.flexWrap = 'wrap';\n      listContainer.style.gap = '12px';\n\n      nutrients.forEach(n => {\n        const key = Object.keys(dailyReference).find(k => n.nutrientName.toLowerCase().includes(k));\n        const max = key ? dailyReference[key] : 100;\n        const pct = Math.min(100, Math.round((n.amount \/ max) * 100));\n        const iconKey = Object.keys(nutrientIcons).find(k => n.nutrientName.toLowerCase().includes(k));\n        const emoji = iconKey ? nutrientIcons[iconKey] : '\ud83d\udd39';\n\n        const tile = document.createElement('div');\n        tile.style.flex = '1 1 220px';\n        tile.style.background = '#fff';\n        tile.style.borderRadius = '10px';\n        tile.style.padding = '10px';\n        tile.style.borderLeft = '6px solid #366f32';\n        tile.style.boxShadow = '0 2px 8px rgba(0,0,0,0.04)';\n        tile.innerHTML = `\n          <div style=\"font-weight:700; color:#2b5a2d; font-size:13px;\">${emoji} ${escapeHtml(n.nutrientName)}<\/div>\n          <div style=\"font-size:13px; color:#444; margin-top:6px;\">${Number(n.amount).toFixed(2)} ${escapeHtml(n.unitName||'')}<\/div>\n          <div style=\"margin-top:8px;\"><div style=\"height:8px; background:linear-gradient(90deg,#f0f7f0,#fafafa); border-radius:8px; overflow:hidden;\"><div style=\"height:100%; width:${pct}%; background:linear-gradient(90deg,#f9d342 0%, #f17f2b 40%, #d63a3a 100%); transition:width .6s;\"><\/div><\/div><\/div>\n        `;\n        listContainer.appendChild(tile);\n      });\n\n      resultsDiv.appendChild(listContainer);\n\n    } catch (err) {\n      console.error(\"Nutrition fetch error:\", err);\n      resultsDiv.innerHTML = \"<div style='padding:12px;color:#b04141;'>Error loading nutrient data. Try again.<\/div>\";\n      topDiv.innerHTML = \"\";\n    }\n  }\n\n  \/\/ close suggestions if clicked outside\n  document.addEventListener('click', (ev) => {\n    const path = ev.composedPath ? ev.composedPath() : (ev.path || []);\n    const inside = path.some(el => el === suggestionsContainer || el === searchInput);\n    if(!inside && !selectedFood) clearSuggestions();\n  });\n\n  \/\/ initial state\n  searchBtn.disabled = true;\n});\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>Nutrition Finder \ud83e\udd57 Powered by USDA \u2013 Search any food to learn about its nutrients. Show \/ Refresh Tip: Use the serving input to scale values (default uses the food&#8217;s serving or 100g).<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-836","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/pages\/836","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yarima.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=836"}],"version-history":[{"count":5,"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/pages\/836\/revisions"}],"predecessor-version":[{"id":988,"href":"https:\/\/yarima.org\/index.php?rest_route=\/wp\/v2\/pages\/836\/revisions\/988"}],"wp:attachment":[{"href":"https:\/\/yarima.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=836"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}