{"id":34902,"date":"2025-11-20T07:16:17","date_gmt":"2025-11-20T15:16:17","guid":{"rendered":"https:\/\/www.podfeet.com\/blog\/?p=34902"},"modified":"2025-11-23T17:36:26","modified_gmt":"2025-11-24T01:36:26","slug":"brett-terpstra-svg-viewer","status":"publish","type":"post","link":"https:\/\/www.podfeet.com\/blog\/2025\/11\/brett-terpstra-svg-viewer\/","title":{"rendered":"Brett Terpstra is My Hero \u2014 BT SVG Viewer Plugin for WordPress"},"content":{"rendered":"<p>I&#8217;m still basking in the glory that is my <a href=\"https:\/\/www.podfeet.com\/blog\/system-settings-macos-26\/\">Mind Map of Doom &#40;\u2122\ufe0fDonald Burr&#41; of every single dang setting in macOS 26<\/a>. In my <a href=\"https:\/\/www.podfeet.com\/blog\/2025\/11\/mind-mapping-mos-settings\/\">how and why I did such a crazy thing post<\/a>, I told you that Brett Terpstra was instrumental in making the mind map actually useful to present on the web, and that I would tell you the story someday. That day is today.<\/p>\n<h2>How I Tricked Brett Into Helping Me<\/h2>\n<p>On Thursday, November 6th, around noon, I sent Brett an email telling him about my mind map and asking him if he had any advice on the best way to present it on the web. I explained that I had a Markdown version, OPML for outlining apps, and I could present it with HTML, but that didn&#8217;t feel right. I wanted it to also be the real mind map in a pretty format, and more importantly, I wanted it to be searchable.<\/p>\n<p>He asked some great questions and even uploaded my mind map to a <a href=\"https:\/\/www.mindmeister.com\/?r=116987\">collaborative mind-mapping service called MindMeister<\/a>. It wasn&#8217;t quite what I was looking for, but his questions made me think more about what I was looking for. I tried to put myself in the eyes of the viewer, not my own. While I think it&#8217;s impressive to look at a <a href=\"https:\/\/www.podfeet.com\/misc\/system-settings-macos-26\/system-settings-macos-26.png\">comically large and unsearchable PNG image<\/a>, it doesn&#8217;t have any value whatsoever to people who just want to figure out where Apple hid a particular setting!<\/p>\n<p>As we noodled, I figured out I could export an SVG version of the mind map. SVG stands for Scalable Vector Graphics, which means that no matter how far you zoom in, the image scales while staying crisp and sharp. Oddly enough, an SVG is a text file that describes the paths to create the image. An SVG would be the perfect format to post this giant mind map to the web.<\/p>\n<p>But embedding an SVG into a blog post proved to be trickier than I imagined.  Brett&#8217;s first idea was,<\/p>\n<blockquote><p>\n  I bet you could pretty easily embed the SVG in a container and then use a tiny bit of JavaScript to center it\u2026 just to avoid people assuming it was a blank image.&#8221;\n<\/p><\/blockquote>\n<p>Brett is a professional developer and could execute that maneuver with his eyes closed. I explained that I would run off with my friend ChatGPT for a while and try to figure it out. Brett offered to take a swing at it if that path didn&#8217;t work out.<\/p>\n<h2>ChatGPT &#8220;Helping&#8221;<\/h2>\n<p>After a couple of hours arguing with my pathological liar friend ChatGPT, I had made no progress. I had several problems to be solved, one of which Brett referred to. The SVG exported from MindNode left a giant white space at the top, so when first viewed in a browser, you would assume you had a blank page. It was only by scrolling around that you&#8217;d eventually discover a small portion of the mind map.<\/p>\n<p>I uploaded the mind map to ChatGPT and asked it to clean things up so the top wasn&#8217;t empty. The resulting file didn&#8217;t have the white space, but it only allowed me to pinch in partway. After abandoning that whole path, ChatGPT told me to embed it in my web page with an image tag (<code>&lt;img&gt;<\/code>), but that turns it into an image, so it&#8217;s not searchable.<\/p>\n<p>Next, it suggested an inline tag for an SVG with some styling provided in CSS format. CSS stands for Cascading Style Sheets, which is the way a website can have consistent formatting for headings, links, graphics, etc., without specifically formatting every item. I wasn&#8217;t sure how this path would work, but I decided to give it a go.<\/p>\n<p>ChatGPT wanted me to paste the <em>entire<\/em> text of the SVG file between the opening and closing <code>&lt;div&gt;<\/code> tags. This SVG file is 7596 lines long, and it probably took 30 seconds just to paste the text into my WordPress interface.<\/p>\n<pre><code class=\"language-html\">&lt;div class=\"svg-inline\"&gt;\n  &lt;!-- paste the entire raw &lt;svg&gt;\u2026&lt;\/svg&gt; file here --&gt;\n&lt;\/div&gt;\n<\/code><\/pre>\n<p>Worse yet, this massive text dump pretty much brought WordPress to its knees. After thinking I&#8217;d saved the post in draft format, when I tried to view the preview, a message popped up asking, &#8220;Do you want to leave this page?&#8221;  That&#8217;s WordPress&#8217;s way of telling you that you have <em>not<\/em> actually saved the page. I gave up on following the suggestions from ChatGPT at this point.<\/p>\n<h2>Brett Jumps In With Both Feet<\/h2>\n<p>You&#8217;ll remember I mentioned that I first asked Brett for advice at noon on Thursday. Friday morning at 8:21 AM, I told Brett that I had failed in my efforts, and he was good to his word and agreed to take a look at a way to solve this for me.<\/p>\n<p>The first thing he did was run my SVG through an optimizer (I had no idea these things existed!), and not only did the optimizer fix the white space problem, but the file size went from 3.8MB down to 1.5MB! I don&#8217;t know which optimizer he used, but a quick search revealed that there are quite a few web-based tools out there to provide this service. I found one called <a href=\"https:\/\/svgomg.net\/\">svgomg.net\/&#8230;<\/a> that has a whole bunch of toggles to help optimize your file the way you want, if you want to see what these tools can do.<\/p>\n<p><em>Anyway<\/em>, his reply came in at 9:40 AM, and in the same message about the optimizer, he wrote:<\/p>\n<blockquote><p>\n  So now I\u2019m about 80% of the way through building you a WordPress plugin to:<\/p>\n<ol>\n<li>Enable SVG uploads to the media library<\/li>\n<li>Add a shortcode to insert SVGs into posts, with attributes like height, initial zoom, max zoom, zoom increment, etc.<\/li>\n<li>Display a viewer in a post\/page with zoom controls and a centering button<\/li>\n<li>Considering building in handling for MindNode SVGs that automates the process of fixing the viewbox and optimizing the SVG, but that might be a 1.1 thing<\/li>\n<\/ol>\n<\/blockquote>\n<p>Now you know why I&#8217;ve been giving you timestamps. I wrote to him at 8:21 AM on Friday, telling him I&#8217;d made no progress, and by 9:40 AM (a mere hour and nineteen minutes later), he was 80% done writing a WordPress plugin to fix my problem. \ud83e\udd2f<\/p>\n<p>At one minute to noon on Thursday, Brett sent me a link to version 1.0.0 of <a href=\"https:\/\/github.com\/ttscoff\/wp-svg-viewer\">his BT SVG Viewer WordPress plugin on GitHub<\/a>. He referred to this as a &#8220;rush&#8221; job, but it wasn&#8217;t just a link to download some code to put on my website. It was a <em>fully documented tool<\/em>. I&#8217;m talking about a Read Me with everything you need to know to use the plugin, including changing the presets for zoom and height.<\/p>\n<p>With his plugin installed on Podfeet&#8217;s WordPress, and a few variables defined by me, the viewer could now see an embedded SVG with buttons to zoom in and out, center the graphic, and reset to the original position. It was a beautiful thing.<\/p>\n<p>Over the next day, Brett released five more versions of the plugin. With his first version, I had to calculate where to center the image when it first came up, and plug those numbers into what WordPress calls a short code. A short code is some text between two square brackets with key\/value pairs. For example, to start at a zoom value of 50%, you would write zoom=&#8221;50&#8243;.  It was a bit tricky to figure out where to center the SVG. I had to know how tall and wide my SVG was so I could calculate the values of <code>center_x=\"3257\"<\/code> and <code>center_y=\"35866\"<\/code>.<\/p>\n<p><code>        <div class=\"bt-svg-viewer-wrapper custom-class controls-position-top controls-mode-both controls-align-left pan-mode-scroll zoom-mode-super_scroll\" id=\"bt-svg-viewer-69f24010263d8\" >\n                        <div class=\"bt-svg-viewer-main controls-position-top controls-mode-both controls-align-left pan-mode-scroll zoom-mode-super_scroll\" data-viewer=\"bt-svg-viewer-69f24010263d8\">\n                                    <div class=\"svg-controls controls-mode-both controls-align-left\" data-viewer=\"bt-svg-viewer-69f24010263d8\">\n                                        <button type=\"button\" class=\"bt-svg-viewer-btn zoom-in-btn\" data-viewer=\"bt-svg-viewer-69f24010263d8\" title=\"Zoom In (Ctrl +)\" aria-label=\"Zoom In\">\n                    <span class=\"btn-icon\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 640 640\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" d=\"M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 176C258.7 176 248 186.7 248 200L248 248L200 248C186.7 248 176 258.7 176 272C176 285.3 186.7 296 200 296L248 296L248 344C248 357.3 258.7 368 272 368C285.3 368 296 357.3 296 344L296 296L344 296C357.3 296 368 285.3 368 272C368 258.7 357.3 248 344 248L296 248L296 200C296 186.7 285.3 176 272 176z\" \/><\/svg><\/span>\n                    <span class=\"btn-text\">Zoom In<\/span>\n                <\/button>\n                            <button type=\"button\" class=\"bt-svg-viewer-btn zoom-out-btn\" data-viewer=\"bt-svg-viewer-69f24010263d8\" title=\"Zoom Out (Ctrl -)\" aria-label=\"Zoom Out\">\n                    <span class=\"btn-icon\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 640 640\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" d=\"M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM200 248C186.7 248 176 258.7 176 272C176 285.3 186.7 296 200 296L344 296C357.3 296 368 285.3 368 272C368 258.7 357.3 248 344 248L200 248z\" \/><\/svg><\/span>\n                    <span class=\"btn-text\">Zoom Out<\/span>\n                <\/button>\n                            <button type=\"button\" class=\"bt-svg-viewer-btn reset-zoom-btn\" data-viewer=\"bt-svg-viewer-69f24010263d8\" title=\"Reset Zoom\" aria-label=\"Reset Zoom\">\n                    <span class=\"btn-icon\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 640 640\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" d=\"M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 416C351.5 416 416 351.5 416 272C416 192.5 351.5 128 272 128C192.5 128 128 192.5 128 272C128 351.5 192.5 416 272 416z\" \/><\/svg><\/span>\n                    <span class=\"btn-text\">Reset Zoom<\/span>\n                <\/button>\n                            <button type=\"button\" class=\"bt-svg-viewer-btn center-view-btn\" data-viewer=\"bt-svg-viewer-69f24010263d8\" title=\"Center View\" aria-label=\"Center View\">\n                    <span class=\"btn-icon\" aria-hidden=\"true\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 640 640\" aria-hidden=\"true\" focusable=\"false\"><path fill=\"currentColor\" d=\"M320 48C337.7 48 352 62.3 352 80L352 98.3C450.1 112.3 527.7 189.9 541.7 288L560 288C577.7 288 592 302.3 592 320C592 337.7 577.7 352 560 352L541.7 352C527.7 450.1 450.1 527.7 352 541.7L352 560C352 577.7 337.7 592 320 592C302.3 592 288 577.7 288 560L288 541.7C189.9 527.7 112.3 450.1 98.3 352L80 352C62.3 352 48 337.7 48 320C48 302.3 62.3 288 80 288L98.3 288C112.3 189.9 189.9 112.3 288 98.3L288 80C288 62.3 302.3 48 320 48zM163.2 352C175.9 414.7 225.3 464.1 288 476.8L288 464C288 446.3 302.3 432 320 432C337.7 432 352 446.3 352 464L352 476.8C414.7 464.1 464.1 414.7 476.8 352L464 352C446.3 352 432 337.7 432 320C432 302.3 446.3 288 464 288L476.8 288C464.1 225.3 414.7 175.9 352 163.2L352 176C352 193.7 337.7 208 320 208C302.3 208 288 193.7 288 176L288 163.2C225.3 175.9 175.9 225.3 163.2 288L176 288C193.7 288 208 302.3 208 320C208 337.7 193.7 352 176 352L163.2 352zM320 272C346.5 272 368 293.5 368 320C368 346.5 346.5 368 320 368C293.5 368 272 346.5 272 320C272 293.5 293.5 272 320 272z\" \/><\/svg><\/span>\n                    <span class=\"btn-text\">Center View<\/span>\n                <\/button>\n                            <button type=\"button\" class=\"bt-svg-viewer-btn coord-copy-btn\" data-viewer=\"bt-svg-viewer-69f24010263d8\" title=\"Copy current center coordinates\" aria-label=\"Copy Center\">\n                    <span class=\"btn-icon\" aria-hidden=\"true\">\ud83d\udccd<\/span>\n                    <span class=\"btn-text\">Copy Center<\/span>\n                <\/button>\n                                        <span class=\"coord-output\" data-viewer=\"bt-svg-viewer-69f24010263d8\" aria-live=\"polite\"><\/span>\n                        <div class=\"divider\"><\/div>\n            <span class=\"zoom-display\">\n                <span class=\"zoom-percentage\" data-viewer=\"bt-svg-viewer-69f24010263d8\">50<\/span>%\n            <\/span>\n        <\/div>                                <div class=\"svg-container\" style=\"height: 100vh\"\n                    data-viewer=\"bt-svg-viewer-69f24010263d8\">\n                    <div class=\"svg-viewport\" data-viewer=\"bt-svg-viewer-69f24010263d8\"\n                        data-svg-url=\"https:\/\/www.podfeet.com\/misc\/system-settings-macos-26\/system-settings-macos-26-brett.svg\">\n                        <!-- SVG will be loaded here -->\n                    <\/div>\n                <\/div>\n            <\/div>\n                                <\/div>\n        <\/code><\/p>\n<p>As he iterated on the plugin code, he removed even that tiny bit of friction. By version 1.0.5, the plugin allowed me to enter all of the values by hand. I could use a preview pane with the same zoom in\/out and center buttons as the viewer sees, and once I get it the way I like it, I can press a button to &#8220;Use Current View for Initial State&#8221;. This removes the burden of figuring out where the center is and entering zoom start points iteratively until it looks right.<\/p>\n<p>There are so many easily understandable controls in Settings for Brett&#8217;s SVG Viewer plugin that it took two long screenshots to illustrate what you can do.<\/p>\n<figure style=\"float: center; margin: 10px\"><img decoding=\"async\" src=\"https:\/\/www.podfeet.com\/blog\/wp-content\/uploads\/2025\/11\/Creating-a-preset-in-svg-preset-viewer-text-editor.png\" alt=\"Creating a preset in svg-preset-viewer showing just the top half where you can type in values and change colors of the buttons for the user\"  title=\"Creating a preset in svg-preset-viewer text editor.png\" width=\"410 \" height=\"600\"><figcaption style=\"text-align:center\">Top Half of Settings for BT SVG Viewer Plugin<\/figcaption><\/figure>\n<figure style=\"float: center; margin: 10px\"><img decoding=\"async\" src=\"https:\/\/www.podfeet.com\/blog\/wp-content\/uploads\/2025\/11\/Creating-a-preset-in-svg-preset-viewer-visual-editor.png\" alt=\"Creating a preset in svg-preset-viewer showing how the buttons will work and look along with a preview of the SVG you&#39;re embedding\"  title=\"Creating a preset in svg-preset-viewer visual editor.png\" width=\"404 \" height=\"600\"><figcaption style=\"text-align:center\">Bottom Half of Settings Shows How Your SVG Will Look<\/figcaption><\/figure>\n<p>In the configuration screen for BT SVG Viewer, he&#8217;s even got a Help page. And I don&#8217;t mean a three-line, get-you-started page. He included a table of contents to walk you through installation, a quick start, a reference guide to the shortcode, and 7 more sections.<\/p>\n<p>I asked him how he managed to write all of this in the elapsed time since we started the project. He explained that he gives ChatGPT his php code and tells <em>it<\/em> to write the documentation. As his next step, he follows the documentation steps written by ChatGPT, which helps him find bugs in his code, all while checking that the documentation makes sense. Brilliant.<\/p>\n<p>Brett&#8217;s plugin is amazing and does so much more than I ever could have even dreamed of asking for. Makes me want to go create some more SVGs!<\/p>\n<p>Hey &#8230; I wonder if I created separate mind maps for each of the 30 top-level sections, whether they would be even more useful to people since they&#8217;d each be more manageable? I know how to embed them now with Brett&#8217;s SVG Viewer Plugin!<\/p>\n<h2>Bottom Line<\/h2>\n<p>Brett Terpstra is a gift to the tech community. In barely more than a day, he had written, documented, and published a plugin for WordPress that solved my problem. The plugin will be available soon from the WordPress.org plugin repository. If you want to get the plugin or peruse his other open source code, go to <a href=\"https:\/\/github.com\/ttscoff\">github.com\/ttscoff<\/a>.<\/p>\n<p>Now here&#8217;s the thing. While Brett is a friend of mine and I successfully tickled his brain with the problem I was trying to solve, he actually codes for a living. Along with writing his own apps like the <a href=\"http:\/\/marked2app.com\">terrific Markdown viewer Marked<\/a>, he\u2019s also recently launched a consulting business called <a href=\"https:\/\/bearandglass.co\">Bear &amp; Glass<\/a>, specializing in Apple and Unix-based automation, and he\u2019d be happy to help you or your business \u2014 bespoke WordPress plugins are definitely an option!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m still basking in the glory that is my Mind Map of Doom &#40;\u2122\ufe0fDonald Burr&#41; of every single dang setting in macOS 26. In my how and why I did such a crazy thing post, I told you that Brett Terpstra was instrumental in making the mind map actually useful to present on the web, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":34907,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[147],"tags":[7660,7672,7668,7673,7669,7670,7666,7671,7674,7667],"class_list":["post-34902","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-posts","tag-brett-terpstra","tag-embedding-svg-in-wordpress","tag-mind-map-visualization","tag-open-source-plugins","tag-scalable-vector-graphics","tag-svg-optimization","tag-svg-viewer","tag-web-design-tools","tag-website-image-optimization","tag-wordpress-plugin"],"jetpack_featured_media_url":"https:\/\/www.podfeet.com\/blog\/wp-content\/uploads\/2025\/11\/System-Settings-embedded-on-Podfeet-with-BT-SVG-Viewer-Controls-1040x520-1.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/posts\/34902","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/comments?post=34902"}],"version-history":[{"count":3,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/posts\/34902\/revisions"}],"predecessor-version":[{"id":34927,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/posts\/34902\/revisions\/34927"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/media\/34907"}],"wp:attachment":[{"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/media?parent=34902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/categories?post=34902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.podfeet.com\/blog\/wp-json\/wp\/v2\/tags?post=34902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}