<br />
<b>Notice</b>:  Function _load_textdomain_just_in_time was called <strong>incorrectly</strong>. Translation loading for the <code>goot</code> domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the <code>init</code> action or later. Please see <a href="https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/">Debugging in WordPress</a> for more information. (This message was added in version 6.7.0.) in <b>/home/vwsymjzkiq/miloperovic.com/wp-includes/functions.php</b> on line <b>6260</b><br />
{"id":1320,"date":"2025-08-13T17:50:08","date_gmt":"2025-08-13T16:50:08","guid":{"rendered":"https:\/\/miloperovic.com\/?page_id=1320"},"modified":"2025-08-14T20:28:35","modified_gmt":"2025-08-14T19:28:35","slug":"estimate","status":"publish","type":"page","link":"https:\/\/miloperovic.com\/?page_id=1320","title":{"rendered":"Estimate"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1320\" class=\"elementor elementor-1320\">\n\t\t\t\t<div class=\"elementor-element elementor-element-67db2fa1 e-con-full e-flex e-con e-parent\" data-id=\"67db2fa1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-79103e47 elementor-widget elementor-widget-goot_work_details_banner\" data-id=\"79103e47\" data-element_type=\"widget\" data-widget_type=\"goot_work_details_banner.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\n\n        <section class=\"wpr-work-details-section hero\">\n            <div class=\"container\">\n\t\t\t\t                <div class=\"sec-title-area\">\n                    <div class=\"sec-left\">\n                                                    <p class=\"sub-title\">Bar, Montenegro<\/p>\n                                                    \n                            <h1 class=\"sec-title\">Esitmate Your Product Roadmap<\/h1>\n                            \n                    <\/div>\n                    <div class=\"sec-right\">\n                        <h4>\n                            <img decoding=\"async\" src=\"https:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg\" alt=\"\">                            BETA TOOL PREVIEW                        <\/h4>\n                                                    <p>\n                                This tool is currently in beta and may overestimate or underestimate the stages time parameter. For the most accurate assessment, please contact me directly.                            <\/p>\n                           \n                    <\/div>\n                <\/div>\n\t\t\t\t   \n                                    \n            <\/div>\n        <\/section>\n        <!-- End Banner Area -->\n               \n                   \n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-48464954 elementor-widget elementor-widget-html\" data-id=\"48464954\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<style>\n  \/* Scoped styles for this widget only *\/\n  #estimator-widget {\n    --bg: #f5f6f7;\n    --text: #000000;\n    --accent: #db4025;\n    --font: Helvetica, Arial, sans-serif;\n    font-family: var(--font);\n  }\n\n  #estimator-widget .wrap { display: grid; place-items: center; padding: 24px; }\n\n  #estimator-widget .card {\n    width: min(900px, 100%);\n    background: #fff;\n    border-radius: 18px;\n    box-shadow: 0 8px 30px rgba(0,0,0,0.08);\n    padding: 20px;\n    border: 1px solid rgba(0,0,0,0.06);\n  }\n\n  #estimator-widget .block { overflow: hidden; transition: max-height .35s ease, opacity .3s ease, margin .35s ease; max-height: 1000px; opacity: 1; margin: 0; }\n  #estimator-widget .card.loading .block { max-height: 0; opacity: 0; margin: 0; }\n\n  #estimator-widget .input-shell {\n    position: relative; background: var(--bg); border-radius: 16px; border: 1px solid rgba(0,0,0,0.08);\n    transition: box-shadow .2s ease, border-color .2s ease; display: flex; align-items: flex-start;\n  }\n  #estimator-widget .input-shell:focus-within { box-shadow: 0 0 0 4px rgba(219,64,37,0.12); border-color: rgba(219,64,37,0.5); }\n\n  #estimator-widget textarea {\n    flex: 1; min-height: 160px; max-height: 360px; resize: vertical; background: transparent; color: var(--text);\n    border: none; outline: none; font-size: 16px; line-height: 1.5; padding: 16px;\n    font-family: var(--font);\n  }\n\n  #estimator-widget .send-btn {\n    position: absolute; right: 10px; bottom: 10px; border: none; background: var(--accent); color: #fff; font-weight: 500; font-size: 14px;\n    padding: 12px 18px; border-radius: 999px; cursor: pointer; display: inline-flex; align-items: center; gap: 10px;\n    box-shadow: 0 8px 18px rgba(219,64,37,0.25); width: auto !important; max-width: max-content; white-space: nowrap;\n    transition: box-shadow 0.25s ease, transform 0.15s ease, opacity .2s ease;\n    font-family: var(--font);\n  }\n  #estimator-widget .send-btn:hover { box-shadow: 0 0 12px rgba(219,64,37,0.6), 0 8px 18px rgba(219,64,37,0.25); transform: translateY(-1px); }\n  #estimator-widget .card.loading .send-btn { pointer-events: none; opacity: .6; }\n\n  #estimator-widget .send-icon { display: inline-block; width: 16px; height: 16px; }\n\n  #estimator-widget .helper { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 24px; margin-bottom: 28px; font-size: 12px; opacity: .7; padding: 0 4px; transition: opacity .3s ease; }\n  #estimator-widget .card.loading .helper { opacity: 0; }\n\n  \/* Loading area *\/\n  #estimator-widget .loading-wrap { position: relative; width: 100%; height: 0; opacity: 0; overflow: hidden; transition: opacity .35s ease .15s, height .35s ease .15s; }\n  #estimator-widget .card.loading .loading-wrap { height: 140px; opacity: 1; }\n  #estimator-widget .card.done .loading-wrap { height: 0; opacity: 0; }\n\n  #estimator-widget .spinner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 28px; height: 28px; border: 3px solid var(--accent); border-right-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; }\n  @keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } }\n\n  \/* Results *\/\n  #estimator-widget .results { opacity: 0; max-height: 0; overflow: hidden; transition: opacity .35s ease, max-height .45s ease; }\n  #estimator-widget .card.done .results { opacity: 1; max-height: 99999px; overflow: visible; }\n\n  #estimator-widget .step { display: grid; grid-template-columns: 90px 1fr 90px; grid-template-areas: \"num text time\"; justify-items: stretch; align-items: start; gap: 16px; background: var(--bg); border: 1px solid rgba(0,0,0,0.06); border-radius: 14px; padding: 18px 20px; box-shadow: 0 4px 16px rgba(0,0,0,0.04); margin-bottom: 12px; }\n  #estimator-widget .step-num { grid-area: num; font-size: 64px; font-weight: 700; color: var(--accent); line-height: 1; display: flex; align-items: flex-start; justify-content: center; justify-self: center; padding-top: 2px; }\n  #estimator-widget .step-text { grid-area: text; color: var(--text); font-size: 16px; line-height: 1.7; opacity: .9; white-space: normal; max-width: 85%; overflow-wrap: anywhere; }\n  #estimator-widget .step-time { grid-area: time; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; justify-self: end; text-align: center; color: var(--text); }\n  #estimator-widget .step-time .label { font-size: 11px; letter-spacing: .08em; opacity: .6; margin-bottom: 4px; }\n  #estimator-widget .step-time .value { font-size: 24px; font-weight: 700; color: var(--accent); line-height: 1.2; margin: 6px 0; }\n  #estimator-widget .step-time .unit { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; opacity: .6; margin-top: 4px; }\n\n  \/* Alerts *\/\n  #estimator-widget .alert { background: var(--bg); border: 1px solid rgba(0,0,0,0.06); border-left: 4px solid var(--accent); color: var(--text); border-radius: 12px; padding: 12px 14px; margin-bottom: 12px; }\n\n  \/* Disclaimer below the card *\/\n  #estimator-widget .ai-disclaimer { font-size: 12px; color: rgba(0,0,0,0.5); margin-top: 12px;\n        max-width: 700px;\n  text-align: center; }\n  \/* overrides per user request *\/\n  #estimator-widget .step { align-items: start; }\n  #estimator-widget .step-num { align-items: flex-start; justify-content: center; padding-top: 2px; }\n  #estimator-widget .step-time { justify-content: flex-start; }\n@media (max-width: 640px) {\n  #estimator-widget .card { padding: 16px; border-radius: 14px; }\n  #estimator-widget .helper { flex-direction: column; align-items: flex-start; gap: 6px; }\n  #estimator-widget .send-btn { right: 8px; bottom: 8px; padding: 10px 14px; font-size: 13px; }\n  #estimator-widget textarea { min-height: 140px; padding-right: 112px; }\n\n  #estimator-widget .step { grid-template-columns: 60px 1fr; grid-template-areas: \"num time\" \"text text\"; gap: 12px; padding: 14px; }\n  #estimator-widget .step-num { font-size: 48px; padding-top: 0; }\n  #estimator-widget .step-time .value { font-size: 20px; }\n  #estimator-widget .step-time .label, #estimator-widget .step-time .unit { font-size: 10px; text-align: center; }\n  #estimator-widget .step-text { max-width: 100%; font-size: 15px; line-height: 1.6; }\n}\n@media (max-width: 640px) {\n  \/* Center the left number vertically with the TIME block, and keep TIME at far right *\/\n  #estimator-widget .step { align-items: center; }\n  #estimator-widget .step-num { display: flex; align-items: center; justify-content: center; }\n  #estimator-widget .step-time { justify-content: center; align-items: center; }\n}\n<\/style>\n\n<div id=\"estimator-widget\">\n  <div class=\"wrap\">\n    <div class=\"card\" id=\"card\">\n      <div class=\"block\" id=\"block\">\n        <form class=\"input-shell\" onsubmit=\"return false;\">\n          <textarea id=\"message\" placeholder=\"Briefly explain your project\"><\/textarea>\n          <button type=\"button\" class=\"send-btn\" id=\"sendBtn\">\n            <svg class=\"send-icon\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" aria-hidden=\"true\">\n              <path d=\"M3 11L21 3L13 21L11 13L3 11Z\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n            <\/svg>\n            Estimate\n          <\/button>\n        <\/form>\n        <div class=\"helper\">\n          <span>Press Ctrl\/\u2318 + Enter to send<\/span>\n          <span class=\"counter\" id=\"counter\">0 \/ 1000<\/span>\n        <\/div>\n      <\/div>\n\n      <div class=\"loading-wrap\">\n        <div class=\"spinner\" aria-label=\"Loading\"><\/div>\n      <\/div>\n\n      <div class=\"results\" id=\"results\"><!-- steps injected here --><\/div>\n    <\/div>\n    <div class=\"ai-disclaimer\">\n      The dataset used for AI estimation is derived from our previous projects. Actual outcomes may vary due to real\u2011world constraints. For the most accurate assessment, please contact us directly for a precise estimation.\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n\/\/ === API helpers from your example (adapted for this widget) ===\nconst API_URL = \"\/ai-estimation\";\n\nasync function estimateProduct(text) {\n  const res = await fetch(API_URL, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\/json\" },\n    body: JSON.stringify({ text })\n  });\n\n  if (!res.ok) {\n    const t = await res.text().catch(() => \"\");\n    throw new Error(`Proxy error ${res.status}: ${t || res.statusText}`);\n  }\n\n  const openaiJson = await res.json();\n  const raw = extractOpenAIText(openaiJson) || \"\";\n  const obj = safeParseJson(raw);\n\n  if (!obj) {\n    return { type: \"error\", error: \"Could not parse JSON from model output\", raw };\n  }\n\n  if (obj.bad === true || obj.bad === \"true\") {\n    return { type: \"bad\", reason: obj.reason || \"Invalid input\" };\n  }\n\n  const stages = [];\n  for (let i = 1; i <= 5; i++) {\n    const s = obj[`stage${i}`];\n    const t = obj[`time${i}`];\n    if (s || t) stages.push({ stage: s || \"\", time: t || \"\" });\n  }\n  return { type: \"ok\", stages, rawObj: obj };\n}\n\nfunction extractOpenAIText(json) {\n  if (!json) return \"\";\n\n  \/\/ 1) Your WP proxy wrapper: { text: \"...\", raw: { ... } }\n  if (typeof json.text === \"string\" && json.text.trim()) return json.text.trim();\n\n  \/\/ 2) If the proxy nests the raw OpenAI response under `raw`, recurse\n  if (json.raw) {\n    const nested = extractOpenAIText(json.raw);\n    if (nested) return nested;\n  }\n\n  \/\/ 3) Responses API convenience field\n  if (typeof json.output_text === \"string\") return json.output_text.trim();\n\n  \/\/ 4) Responses API generic shape\n  if (Array.isArray(json?.output)) {\n    const parts = [];\n    for (const item of json.output) {\n      const content = item?.content || [];\n      for (const c of content) {\n        if (typeof c?.text === \"string\") parts.push(c.text);\n        if (typeof c?.content === \"string\") parts.push(c.content);\n      }\n    }\n    if (parts.length) return parts.join(\"\\n\").trim();\n  }\n\n  \/\/ 5) Chat Completions fallback\n  if (json?.choices?.[0]?.message?.content) {\n    return String(json.choices[0].message.content).trim();\n  }\n\n  \/\/ 6) Last resort\n  if (typeof json === \"string\") return json.trim();\n  return \"\";\n}\n\nfunction safeParseJson(text) {\n  if (!text) return null;\n  const fenced = text.trim().replace(\/^```json\\s*|\\s*```$\/g, \"\");\n  try { return JSON.parse(fenced); } catch (_) {}\n  const candidate = extractFirstJsonObject(text);\n  if (candidate) {\n    try { return JSON.parse(candidate); } catch (_) {}\n  }\n  return null;\n}\n\nfunction extractFirstJsonObject(s) {\n  const start = s.indexOf(\"{\");\n  if (start === -1) return null;\n  let depth = 0, inStr = false, esc = false;\n  for (let i = start; i < s.length; i++) {\n    const ch = s[i];\n    if (inStr) {\n      if (esc) { esc = false; }\n      else if (ch === \"\\\\\") { esc = true; }\n      else if (ch === \"\\\"\") { inStr = false; }\n    } else {\n      if (ch === \"\\\"\") inStr = true;\n      else if (ch === \"{\") depth++;\n      else if (ch === \"}\") {\n        depth--;\n        if (depth === 0) return s.slice(start, i + 1);\n      }\n    }\n  }\n  return null;\n}\n\n\/\/ === Widget behavior ===\n(function initEstimatorWidget(){\n  const root = document.getElementById('estimator-widget');\n  if (!root || root.dataset.bound) return; \/\/ avoid double-binding in Elementor\n  root.dataset.bound = '1';\n\n  const card = root.querySelector('#card');\n  const textarea = root.querySelector('#message');\n  const counter = root.querySelector('#counter');\n  const sendBtn = root.querySelector('#sendBtn');\n  const results = root.querySelector('#results');\n\n  function updateCounter(){\n    if (!textarea || !counter) return; \/\/ guard against sandbox timing\n    const max = 1000;\n    const len = (textarea.value || '').length;\n    counter.textContent = `${len} \/ ${max}`;\n  }\n\n  function parseTimeParts(t){\n    if (!t) return { num: '\u2014', unit: 'DAYS' };\n    const s = String(t).trim().toLowerCase();\n    const numMatch = s.match(\/(~?\\s*)?(\\d+[\\s\\-\u2013]*\\d+|\\d+)\/);\n    let num = numMatch ? numMatch[2] : '\u2014';\n    num = num.replace(\/\\s*-\\s*\/g, '\u2013');\n\n    let unit = 'DAYS';\n    if (\/(hour|hr|hrs|h)\\b\/.test(s)) unit = 'HOURS';\n    else if (\/(week|wks|wk|w)\\b\/.test(s)) unit = 'WEEKS';\n    else if (\/(month|mo|mos)\\b\/.test(s)) unit = 'MONTHS';\n    else if (\/(day|d)\\b\/.test(s)) unit = 'DAYS';\n\n    unit = 'WEEKS';\n    return { num, unit };\n  }\n\n  function renderResults(result){\n    results.innerHTML = '';\n\n    if (result.type === 'error') {\n      const div = document.createElement('div');\n      div.className = 'alert';\n      div.textContent = `Error: ${result.error}`;\n      results.appendChild(div);\n      return;\n    }\n    if (result.type === 'bad') {\n      const div = document.createElement('div');\n      div.className = 'alert';\n      div.textContent = `Input not suitable: ${result.reason}`;\n      results.appendChild(div);\n      return;\n    }\n\n    \/\/ Always render 5 boxes, filling blanks if API returned fewer\n    for (let i = 1; i <= 5; i++) {\n      const data = result.stages && result.stages[i-1] ? result.stages[i-1] : { stage: '', time: '' };\n      const { num, unit } = parseTimeParts(data.time);\n      const text = data.stage || '\u2014';\n\n      const step = document.createElement('div');\n      step.className = 'step';\n      step.innerHTML = `\n        <div class=\"step-num\">${i}<\/div>\n        <div class=\"step-text\">${text}<\/div>\n        <div class=\"step-time\"><span class=\"label\">TIME<\/span><span class=\"value\">${num}<\/span><span class=\"unit\">${unit}<\/span><\/div>`;\n      results.appendChild(step);\n    }\n  }\n\n  async function handleEstimate(){\n    const text = (textarea && textarea.value ? textarea.value : '').trim();\n    if (!text) return;\n    if (card.classList.contains('loading')) return; \/\/ prevent double-submits\n\n    \/\/ Reset previous state so spinner shows like the first time\n    card.classList.remove('done');\n    results.innerHTML = '';\n\n    \/\/ Show spinner\n    card.classList.add('loading');\n    try {\n      const result = await estimateProduct(text);\n      card.classList.remove('loading');\n      card.classList.add('done');\n      renderResults(result);\n    } catch (e) {\n      card.classList.remove('loading');\n      card.classList.add('done');\n      renderResults({ type: 'error', error: e.message || String(e) });\n    }\n  }\n\n  if (textarea) {\n    textarea.addEventListener('input', updateCounter);\n    textarea.addEventListener('keydown', (e) => {\n      if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); handleEstimate(); }\n    });\n  }\n  if (sendBtn) sendBtn.addEventListener('click', handleEstimate);\n\n  \/\/ Initialize counter safely (only if textarea exists)\n  if (textarea) updateCounter();\n\n  \/\/ Minimal self-tests for parser\/extractor (console only)\n  (function selfTests(){\n    try {\n      \/\/ Test A: Your proxy shape with `text` and nested `raw`\n      const sample = {\n        text: '{\"stage1\":\"A\",\"time1\":\"1 week\",\"stage2\":\"B\",\"time2\":\"2 weeks\",\"stage3\":\"C\",\"time3\":\"3 weeks\",\"stage4\":\"D\",\"time4\":\"4 weeks\",\"stage5\":\"E\",\"time5\":\"5 weeks\"}',\n        raw: { output_text: 'ignored when `text` present' }\n      };\n      const a = extractOpenAIText(sample);\n      const aObj = safeParseJson(a);\n      console.log('[Test A] Extract+Parse:', a && aObj ? 'PASS' : 'FAIL');\n\n      \/\/ Test B: Responses API `output_text`\n      const b = extractOpenAIText({ output_text: '{\"stage1\":\"A\",\"time1\":\"1 day\"}' });\n      console.log('[Test B] Output_text:', b ? 'PASS' : 'FAIL');\n\n      \/\/ Test C: Chat Completions fallback\n      const c = extractOpenAIText({ choices: [{ message: { content: '{\"bad\":\"true\",\"reason\":\"No\"}' } }] });\n      const cObj = safeParseJson(c);\n      console.log('[Test C] Chat fallback parse:', cObj && cObj.bad ? 'PASS' : 'FAIL');\n    } catch(err) { console.warn('Self-tests error:', err); }\n  })();\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4b745292 elementor-widget elementor-widget-spacer\" data-id=\"4b745292\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Bar, Montenegro Esitmate Your Product Roadmap BETA TOOL PREVIEW This tool is currently in beta and may overestimate or underestimate the stages time parameter. For the most accurate assessment, please contact me directly. Estimate Press Ctrl\/\u2318 + Enter to send 0 \/ 1000 The dataset used for AI estimation is derived from our previous projects&#8230;.<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-builder.php","meta":{"footnotes":""},"class_list":["post-1320","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Estimate - miloperovic<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/miloperovic.com\/?page_id=1320\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Estimate - miloperovic\" \/>\n<meta property=\"og:description\" content=\"Bar, Montenegro Esitmate Your Product Roadmap BETA TOOL PREVIEW This tool is currently in beta and may overestimate or underestimate the stages time parameter. For the most accurate assessment, please contact me directly. Estimate Press Ctrl\/\u2318 + Enter to send 0 \/ 1000 The dataset used for AI estimation is derived from our previous projects....\" \/>\n<meta property=\"og:url\" content=\"https:\/\/miloperovic.com\/?page_id=1320\" \/>\n<meta property=\"og:site_name\" content=\"miloperovic\" \/>\n<meta property=\"article:modified_time\" content=\"2025-08-14T19:28:35+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320\",\"url\":\"https:\/\/miloperovic.com\/?page_id=1320\",\"name\":\"Estimate - miloperovic\",\"isPartOf\":{\"@id\":\"https:\/\/miloperovic.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320#primaryimage\"},\"image\":{\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320#primaryimage\"},\"thumbnailUrl\":\"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg\",\"datePublished\":\"2025-08-13T16:50:08+00:00\",\"dateModified\":\"2025-08-14T19:28:35+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/miloperovic.com\/?page_id=1320\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320#primaryimage\",\"url\":\"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg\",\"contentUrl\":\"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/miloperovic.com\/?page_id=1320#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/miloperovic.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Estimate\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/miloperovic.com\/#website\",\"url\":\"https:\/\/miloperovic.com\/\",\"name\":\"miloperovic\",\"description\":\"Milo Perovic\",\"publisher\":{\"@id\":\"https:\/\/miloperovic.com\/#\/schema\/person\/da4a3f1fccc483635b4a9a0710f48654\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/miloperovic.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/miloperovic.com\/#\/schema\/person\/da4a3f1fccc483635b4a9a0710f48654\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/miloperovic.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/miloperovic.com\/wp-content\/uploads\/2025\/06\/whAsset-5@3x-1.png\",\"contentUrl\":\"https:\/\/miloperovic.com\/wp-content\/uploads\/2025\/06\/whAsset-5@3x-1.png\",\"width\":156,\"height\":65,\"caption\":\"admin\"},\"logo\":{\"@id\":\"https:\/\/miloperovic.com\/#\/schema\/person\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Estimate - miloperovic","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/miloperovic.com\/?page_id=1320","og_locale":"en_US","og_type":"article","og_title":"Estimate - miloperovic","og_description":"Bar, Montenegro Esitmate Your Product Roadmap BETA TOOL PREVIEW This tool is currently in beta and may overestimate or underestimate the stages time parameter. For the most accurate assessment, please contact me directly. Estimate Press Ctrl\/\u2318 + Enter to send 0 \/ 1000 The dataset used for AI estimation is derived from our previous projects....","og_url":"https:\/\/miloperovic.com\/?page_id=1320","og_site_name":"miloperovic","article_modified_time":"2025-08-14T19:28:35+00:00","og_image":[{"url":"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/miloperovic.com\/?page_id=1320","url":"https:\/\/miloperovic.com\/?page_id=1320","name":"Estimate - miloperovic","isPartOf":{"@id":"https:\/\/miloperovic.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/miloperovic.com\/?page_id=1320#primaryimage"},"image":{"@id":"https:\/\/miloperovic.com\/?page_id=1320#primaryimage"},"thumbnailUrl":"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg","datePublished":"2025-08-13T16:50:08+00:00","dateModified":"2025-08-14T19:28:35+00:00","breadcrumb":{"@id":"https:\/\/miloperovic.com\/?page_id=1320#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/miloperovic.com\/?page_id=1320"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/miloperovic.com\/?page_id=1320#primaryimage","url":"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg","contentUrl":"http:\/\/miloperovic.com\/wp-content\/uploads\/2024\/12\/svgviewer-output-5.svg"},{"@type":"BreadcrumbList","@id":"https:\/\/miloperovic.com\/?page_id=1320#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/miloperovic.com\/"},{"@type":"ListItem","position":2,"name":"Estimate"}]},{"@type":"WebSite","@id":"https:\/\/miloperovic.com\/#website","url":"https:\/\/miloperovic.com\/","name":"miloperovic","description":"Milo Perovic","publisher":{"@id":"https:\/\/miloperovic.com\/#\/schema\/person\/da4a3f1fccc483635b4a9a0710f48654"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/miloperovic.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/miloperovic.com\/#\/schema\/person\/da4a3f1fccc483635b4a9a0710f48654","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/miloperovic.com\/#\/schema\/person\/image\/","url":"https:\/\/miloperovic.com\/wp-content\/uploads\/2025\/06\/whAsset-5@3x-1.png","contentUrl":"https:\/\/miloperovic.com\/wp-content\/uploads\/2025\/06\/whAsset-5@3x-1.png","width":156,"height":65,"caption":"admin"},"logo":{"@id":"https:\/\/miloperovic.com\/#\/schema\/person\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/pages\/1320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/miloperovic.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1320"}],"version-history":[{"count":22,"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/pages\/1320\/revisions"}],"predecessor-version":[{"id":1360,"href":"https:\/\/miloperovic.com\/index.php?rest_route=\/wp\/v2\/pages\/1320\/revisions\/1360"}],"wp:attachment":[{"href":"https:\/\/miloperovic.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}