{"id":159,"date":"2026-06-16T14:56:31","date_gmt":"2026-06-16T19:56:31","guid":{"rendered":"https:\/\/overthinking.tech\/?page_id=159"},"modified":"2026-06-16T14:56:31","modified_gmt":"2026-06-16T19:56:31","slug":"custom-posts","status":"publish","type":"page","link":"https:\/\/overthinking.tech\/?page_id=159","title":{"rendered":"Custom Posts"},"content":{"rendered":"\n<!-- Overthinking Tech Posts Page - WordPress Custom HTML Block -->\n<section class=\"ott-posts-page\" aria-label=\"Overthinking Tech Posts\">\n  <style>\n    .ott-posts-page {\n      --ott-bg: #07131f;\n      --ott-panel: rgba(12, 27, 43, 0.92);\n      --ott-panel-2: rgba(15, 39, 60, 0.88);\n      --ott-border: rgba(96, 202, 255, 0.22);\n      --ott-text: #eef8ff;\n      --ott-muted: #b7c9d8;\n      --ott-cyan: #61d7ff;\n      --ott-blue: #2f80ff;\n      --ott-green: #62f0c0;\n      --ott-shadow: 0 22px 60px rgba(0, 0, 0, 0.32);\n      color: var(--ott-text);\n      background:\n        radial-gradient(circle at 18% 0%, rgba(97, 215, 255, 0.20), transparent 34rem),\n        radial-gradient(circle at 88% 12%, rgba(47, 128, 255, 0.18), transparent 36rem),\n        linear-gradient(135deg, #06101b 0%, #081928 48%, #06101b 100%);\n      border-radius: 28px;\n      padding: clamp(20px, 4vw, 54px);\n      margin: 0 auto;\n      max-width: 1180px;\n      overflow: hidden;\n      position: relative;\n      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      box-sizing: border-box;\n    }\n\n    .ott-posts-page,\n    .ott-posts-page * {\n      box-sizing: border-box;\n    }\n\n    .ott-posts-page::before {\n      content: \"\";\n      position: absolute;\n      inset: 0;\n      background-image:\n        linear-gradient(rgba(97, 215, 255, 0.055) 1px, transparent 1px),\n        linear-gradient(90deg, rgba(97, 215, 255, 0.055) 1px, transparent 1px);\n      background-size: 34px 34px;\n      mask-image: linear-gradient(to bottom, rgba(0,0,0,0.8), transparent 75%);\n      pointer-events: none;\n    }\n\n    .ott-posts-inner {\n      position: relative;\n      z-index: 1;\n    }\n\n    .ott-posts-toolbar {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 16px;\n      margin-bottom: 22px;\n      padding-bottom: 18px;\n      border-bottom: 1px solid var(--ott-border);\n    }\n\n    .ott-posts-mini-brand {\n      display: flex;\n      align-items: center;\n      gap: 12px;\n      min-width: 0;\n    }\n\n    .ott-posts-mini-logo {\n      width: 42px;\n      height: 42px;\n      object-fit: contain;\n      border-radius: 12px;\n      background: rgba(255,255,255,0.06);\n      border: 1px solid rgba(255,255,255,0.12);\n      padding: 5px;\n    }\n\n    .ott-posts-mini-title {\n      margin: 0;\n      font-size: clamp(1.15rem, 2.4vw, 1.65rem);\n      line-height: 1.1;\n      letter-spacing: -0.03em;\n      color: var(--ott-text);\n    }\n\n    .ott-posts-mini-subtitle {\n      margin: 4px 0 0;\n      color: var(--ott-muted);\n      font-size: 0.95rem;\n      line-height: 1.35;\n    }\n\n    .ott-posts-contact {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      justify-content: flex-end;\n    }\n\n    .ott-pill {\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      color: var(--ott-text);\n      text-decoration: none !important;\n      border: 1px solid var(--ott-border);\n      background: rgba(255,255,255,0.055);\n      border-radius: 999px;\n      padding: 9px 12px;\n      font-weight: 700;\n      font-size: 0.9rem;\n      white-space: nowrap;\n    }\n\n    .ott-pill:hover,\n    .ott-pill:focus {\n      border-color: rgba(97, 215, 255, 0.6);\n      background: rgba(97, 215, 255, 0.12);\n      color: #ffffff;\n    }\n\n    .ott-posts-status {\n      color: var(--ott-muted);\n      font-size: 1rem;\n      padding: 26px;\n      border: 1px solid var(--ott-border);\n      background: var(--ott-panel);\n      border-radius: 22px;\n      box-shadow: var(--ott-shadow);\n    }\n\n    .ott-posts-grid {\n      display: grid;\n      grid-template-columns: repeat(3, minmax(0, 1fr));\n      gap: 18px;\n      align-items: stretch;\n    }\n\n    .ott-post-card {\n      min-height: 100%;\n      display: flex;\n      flex-direction: column;\n      overflow: hidden;\n      border-radius: 24px;\n      border: 1px solid var(--ott-border);\n      background: linear-gradient(180deg, var(--ott-panel-2), var(--ott-panel));\n      box-shadow: var(--ott-shadow);\n      color: var(--ott-text);\n      text-decoration: none !important;\n      transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;\n    }\n\n    .ott-post-card:hover,\n    .ott-post-card:focus {\n      transform: translateY(-3px);\n      border-color: rgba(97, 215, 255, 0.62);\n      color: var(--ott-text);\n    }\n\n    .ott-post-image-wrap {\n      aspect-ratio: 16 \/ 9;\n      background:\n        linear-gradient(135deg, rgba(97, 215, 255, 0.22), rgba(47, 128, 255, 0.11)),\n        linear-gradient(135deg, #0d2235, #07131f);\n      border-bottom: 1px solid var(--ott-border);\n      position: relative;\n      overflow: hidden;\n    }\n\n    .ott-post-image-wrap::after {\n      content: \"\";\n      position: absolute;\n      inset: 0;\n      background-image:\n        linear-gradient(rgba(255,255,255,0.055) 1px, transparent 1px),\n        linear-gradient(90deg, rgba(255,255,255,0.055) 1px, transparent 1px);\n      background-size: 22px 22px;\n      opacity: 0.75;\n    }\n\n    .ott-post-image {\n      width: 100%;\n      height: 100%;\n      object-fit: cover;\n      display: block;\n      position: relative;\n      z-index: 1;\n    }\n\n    .ott-post-placeholder {\n      position: absolute;\n      inset: 0;\n      z-index: 1;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 22px;\n      text-align: center;\n      color: rgba(238, 248, 255, 0.78);\n      font-weight: 900;\n      letter-spacing: -0.03em;\n      font-size: clamp(1.1rem, 2.2vw, 1.5rem);\n    }\n\n    .ott-post-body {\n      padding: 20px;\n      display: flex;\n      flex-direction: column;\n      gap: 12px;\n      flex: 1;\n    }\n\n    .ott-post-meta {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      align-items: center;\n      color: var(--ott-green);\n      font-size: 0.82rem;\n      font-weight: 800;\n      text-transform: uppercase;\n      letter-spacing: 0.08em;\n    }\n\n    .ott-post-title {\n      margin: 0;\n      color: var(--ott-text);\n      font-size: clamp(1.15rem, 2vw, 1.45rem);\n      line-height: 1.15;\n      letter-spacing: -0.035em;\n    }\n\n    .ott-post-excerpt {\n      margin: 0;\n      color: var(--ott-muted);\n      line-height: 1.58;\n      font-size: 0.98rem;\n      flex: 1;\n    }\n\n    .ott-post-readmore {\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      color: var(--ott-cyan);\n      font-weight: 900;\n      margin-top: 6px;\n    }\n\n    .ott-posts-empty a,\n    .ott-posts-status a {\n      color: var(--ott-cyan);\n      font-weight: 800;\n    }\n\n    .ott-load-more-wrap {\n      display: flex;\n      justify-content: center;\n      margin-top: 24px;\n    }\n\n    .ott-load-more {\n      appearance: none;\n      border: 1px solid rgba(97, 215, 255, 0.42);\n      background: linear-gradient(135deg, rgba(97, 215, 255, 0.18), rgba(47, 128, 255, 0.20));\n      color: #ffffff;\n      border-radius: 999px;\n      padding: 12px 18px;\n      font-weight: 900;\n      cursor: pointer;\n      box-shadow: 0 12px 28px rgba(0,0,0,0.24);\n    }\n\n    .ott-load-more:hover,\n    .ott-load-more:focus {\n      border-color: rgba(97, 215, 255, 0.7);\n      background: linear-gradient(135deg, rgba(97, 215, 255, 0.24), rgba(47, 128, 255, 0.28));\n    }\n\n    .ott-load-more[disabled] {\n      opacity: 0.55;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 920px) {\n      .ott-posts-grid {\n        grid-template-columns: repeat(2, minmax(0, 1fr));\n      }\n\n      .ott-posts-toolbar {\n        align-items: flex-start;\n        flex-direction: column;\n      }\n\n      .ott-posts-contact {\n        justify-content: flex-start;\n      }\n    }\n\n    @media (max-width: 620px) {\n      .ott-posts-page {\n        border-radius: 18px;\n        padding: 18px;\n      }\n\n      .ott-posts-grid {\n        grid-template-columns: 1fr;\n      }\n\n      .ott-posts-mini-logo {\n        width: 38px;\n        height: 38px;\n      }\n    }\n  <\/style>\n\n  <div class=\"ott-posts-inner\">\n    <div class=\"ott-posts-toolbar\">\n      <div class=\"ott-posts-mini-brand\">\n        <img decoding=\"async\" class=\"ott-posts-mini-logo\" src=\"https:\/\/overthinking.tech\/wp-content\/uploads\/2025\/02\/cropped-SmallIcon_3.png\" alt=\"Overthinking Tech logo\" loading=\"lazy\">\n        <div>\n          <h1 class=\"ott-posts-mini-title\">Overthinking Tech Posts<\/h1>\n          <p class=\"ott-posts-mini-subtitle\">Practical notes for small business technology decisions.<\/p>\n        <\/div>\n      <\/div>\n      <div class=\"ott-posts-contact\" aria-label=\"Contact Overthinking Tech\">\n        <a class=\"ott-pill\" href=\"tel:16513593318\">651-359-3318<\/a>\n        <a class=\"ott-pill\" href=\"mailto:jonathan@overthinking.tech\">jonathan@overthinking.tech<\/a>\n      <\/div>\n    <\/div>\n\n    <div id=\"ott-posts-status\" class=\"ott-posts-status\">Loading posts\u2026<\/div>\n    <div id=\"ott-posts-grid\" class=\"ott-posts-grid\" aria-live=\"polite\"><\/div>\n    <div id=\"ott-load-more-wrap\" class=\"ott-load-more-wrap\" hidden=\"\">\n      <button id=\"ott-load-more\" class=\"ott-load-more\" type=\"button\">Load more posts<\/button>\n    <\/div>\n  <\/div>\n\n  <script>\n    (function () {\n      const postsGrid = document.getElementById(\"ott-posts-grid\");\n      const statusBox = document.getElementById(\"ott-posts-status\");\n      const loadMoreWrap = document.getElementById(\"ott-load-more-wrap\");\n      const loadMoreButton = document.getElementById(\"ott-load-more\");\n\n      const postsPerPage = 9;\n      let currentPage = 1;\n      let totalPages = 1;\n      let isLoading = false;\n\n      function stripHtml(html) {\n        const div = document.createElement(\"div\");\n        div.innerHTML = html || \"\";\n        return (div.textContent || div.innerText || \"\").trim();\n      }\n\n      function decodeHtml(html) {\n        const textarea = document.createElement(\"textarea\");\n        textarea.innerHTML = html || \"\";\n        return textarea.value;\n      }\n\n      function formatDate(dateString) {\n        if (!dateString) return \"\";\n        const date = new Date(dateString);\n        if (Number.isNaN(date.getTime())) return \"\";\n        return date.toLocaleDateString(undefined, {\n          year: \"numeric\",\n          month: \"short\",\n          day: \"numeric\"\n        });\n      }\n\n      function getFeaturedImage(post) {\n        try {\n          const media = post._embedded && post._embedded[\"wp:featuredmedia\"] && post._embedded[\"wp:featuredmedia\"][0];\n          if (!media) return \"\";\n          if (media.media_details && media.media_details.sizes) {\n            const sizes = media.media_details.sizes;\n            return (sizes.large && sizes.large.source_url) ||\n                   (sizes.medium_large && sizes.medium_large.source_url) ||\n                   (sizes.medium && sizes.medium.source_url) ||\n                   media.source_url || \"\";\n          }\n          return media.source_url || \"\";\n        } catch (error) {\n          return \"\";\n        }\n      }\n\n      function makePostCard(post) {\n        const articleUrl = post.link || \"#\";\n        const title = decodeHtml(post.title && post.title.rendered ? post.title.rendered : \"Untitled post\");\n        const date = formatDate(post.date);\n        const excerpt = stripHtml(post.excerpt && post.excerpt.rendered ? post.excerpt.rendered : \"\");\n        const imageUrl = getFeaturedImage(post);\n\n        const card = document.createElement(\"a\");\n        card.className = \"ott-post-card\";\n        card.href = articleUrl;\n        card.setAttribute(\"aria-label\", \"Read post: \" + title);\n\n        const imageWrap = document.createElement(\"div\");\n        imageWrap.className = \"ott-post-image-wrap\";\n\n        if (imageUrl) {\n          const img = document.createElement(\"img\");\n          img.className = \"ott-post-image\";\n          img.src = imageUrl;\n          img.alt = \"\";\n          img.loading = \"lazy\";\n          imageWrap.appendChild(img);\n        } else {\n          const placeholder = document.createElement(\"div\");\n          placeholder.className = \"ott-post-placeholder\";\n          placeholder.textContent = \"Overthinking Tech\";\n          imageWrap.appendChild(placeholder);\n        }\n\n        const body = document.createElement(\"div\");\n        body.className = \"ott-post-body\";\n\n        const meta = document.createElement(\"div\");\n        meta.className = \"ott-post-meta\";\n        meta.textContent = date || \"Overthinking Tech\";\n\n        const h2 = document.createElement(\"h2\");\n        h2.className = \"ott-post-title\";\n        h2.textContent = title;\n\n        const p = document.createElement(\"p\");\n        p.className = \"ott-post-excerpt\";\n        p.textContent = excerpt || \"Read the latest practical note from Overthinking Tech.\";\n\n        const readMore = document.createElement(\"span\");\n        readMore.className = \"ott-post-readmore\";\n        readMore.textContent = \"Read post \u2192\";\n\n        body.appendChild(meta);\n        body.appendChild(h2);\n        body.appendChild(p);\n        body.appendChild(readMore);\n\n        card.appendChild(imageWrap);\n        card.appendChild(body);\n\n        return card;\n      }\n\n      function setStatus(message, isError) {\n        statusBox.hidden = false;\n        statusBox.className = isError ? \"ott-posts-status ott-posts-empty\" : \"ott-posts-status\";\n        statusBox.innerHTML = message;\n      }\n\n      function hideStatus() {\n        statusBox.hidden = true;\n      }\n\n      function fetchPosts(page) {\n        if (isLoading) return;\n        isLoading = true;\n        loadMoreButton.disabled = true;\n\n        if (page === 1) {\n          setStatus(\"Loading posts\u2026\", false);\n        } else {\n          loadMoreButton.textContent = \"Loading\u2026\";\n        }\n\n        const postsUrl = \"https:\/\/overthinking.tech\/?rest_route=\/wp\/v2\/posts&_embed=1&per_page=\" + postsPerPage + \"&page=\" + page;\n\n        fetch(postsUrl)\n          .then(function (response) {\n            totalPages = parseInt(response.headers.get(\"X-WP-TotalPages\") || \"1\", 10);\n            if (!response.ok) {\n              throw new Error(\"WordPress returned \" + response.status);\n            }\n            return response.json();\n          })\n          .then(function (posts) {\n            if (!Array.isArray(posts) || posts.length === 0) {\n              if (page === 1) {\n                setStatus(\"No posts are published yet.\", false);\n              }\n              loadMoreWrap.hidden = true;\n              return;\n            }\n\n            hideStatus();\n            posts.forEach(function (post) {\n              postsGrid.appendChild(makePostCard(post));\n            });\n\n            currentPage = page;\n            if (currentPage < totalPages) {\n              loadMoreWrap.hidden = false;\n              loadMoreButton.disabled = false;\n              loadMoreButton.textContent = \"Load more posts\";\n            } else {\n              loadMoreWrap.hidden = true;\n            }\n          })\n          .catch(function (error) {\n            console.error(\"Error loading Overthinking Tech posts:\", error);\n            setStatus(\n              \"Posts could not be loaded. Check that the WordPress REST URL is reachable: <a href=\\\"https:\/\/overthinking.tech\/?rest_route=\/wp\/v2\/posts\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">test the posts feed<\/a>.\",\n              true\n            );\n            loadMoreWrap.hidden = true;\n          })\n          .finally(function () {\n            isLoading = false;\n            loadMoreButton.disabled = false;\n            if (currentPage < totalPages) {\n              loadMoreButton.textContent = \"Load more posts\";\n            }\n          });\n      }\n\n      loadMoreButton.addEventListener(\"click\", function () {\n        fetchPosts(currentPage + 1);\n      });\n\n      fetchPosts(1);\n    })();\n  <\/script>\n<\/section>\n","protected":false},"excerpt":{"rendered":"<p>Overthinking Tech Posts Practical notes for small business technology decisions. 651-359-3318 jonathan@overthinking.tech Loading posts\u2026 Load more posts<\/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-159","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/pages\/159","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/overthinking.tech\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=159"}],"version-history":[{"count":1,"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/pages\/159\/revisions"}],"predecessor-version":[{"id":161,"href":"https:\/\/overthinking.tech\/index.php?rest_route=\/wp\/v2\/pages\/159\/revisions\/161"}],"wp:attachment":[{"href":"https:\/\/overthinking.tech\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=159"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}