<!DOCTYPE html>
<html data-bs-theme="light" lang="en-au">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Tools | Nathan.Woodburn/</title>
    <meta name="theme-color" content="#000000">
    <link rel="canonical" href="https://nathan.woodburn.au/tools">
    <meta property="og:url" content="https://nathan.woodburn.au/tools">
    <meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
    <meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
    <meta property="og:title" content="Nathan.Woodburn/">
    <meta name="twitter:card" content="summary">
    <meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
    <meta property="og:type" content="website">
    <meta name="twitter:title" content="Nathan.Woodburn/">
    <meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
    <meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
    <meta name="description" content="Check out some tools I use">
    <link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
    <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
    <link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
    <link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
    <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&amp;display=swap">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&amp;display=swap">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&amp;display=swap">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&amp;display=swap">
    <link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
    <link rel="stylesheet" href="/assets/css/styles.min.css">
    <link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
    <link rel="stylesheet" href="/assets/css/profile.min.css">
    <link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
    <link rel="stylesheet" href="/assets/css/tools.min.css">
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
</head>

<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77">
    <nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
        <div class="container-fluid"><a class="navbar-brand" href="/#">
                <div style="padding-right: 1em;display: inline-flex;">
                    <div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
                </div>
            </a><button class="navbar-toggler navbar-toggler-right" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
            <div class="collapse navbar-collapse" id="navbarResponsive">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
                    <li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
                    <li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
                    <li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
                    <li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <header class="masthead" style="background: url(&quot;/assets/img/bg/projects.webp&quot;) bottom / cover no-repeat;height: auto;padding-top: 20px;">
        <div style="margin-top: 150px;margin-bottom: 100px;">
            <div class="container">
                <div class="row">
                    <div class="col-lg-8 mx-auto">
                        <h1 class="brand-heading">Tools</h1>
                        <p>Here is a list of applications, tools and services I use regularly.</p>
                    </div>
                </div>
            </div>
        </div>
    </header>
    <section class="text-center content-section" id="tools" style="padding-bottom: 100px;">
        <div class="container">{% for type, tools_in_type in tools | groupby('type') %}
<h2 class="mt-4 mb-3 sticky-top bg-primary py-2 section-header" id="{{type}}">{{ type }}</h2>
<div class="row">
    {% for tool in tools_in_type %}
    <div class="col-md-6 col-lg-4 mb-4">
        <div class="card h-100 shadow-sm transition-all" style="transition: transform 0.2s, box-shadow 0.2s;">
            <div class="card-body d-flex flex-column">
                <h4 class="card-title">{{tool.name}}</h4>
                <p class="card-text">{{ tool.description }}</p>
                <div class="btn-group gap-3 mt-auto" role="group">{% if tool.demo %}<button class="btn btn-primary"
                        type="button" data-bs-target="#modal-{{tool.name}}" data-bs-toggle="modal"
                        style="transition: transform 0.2s, background-color 0.2s;">View Demo</button>{% endif %}<a
                        class="btn btn-primary" role="button" href="{{tool.url}}" target="_blank"
                        style="transition: transform 0.2s, background-color 0.2s;">{{tool.name}} Website</a></div>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
<!-- Modals for this type -->
{% for tool in tools_in_type %}
{% if tool.demo %}
<div id="modal-{{tool.name}}" class="modal fade" role="dialog" tabindex="-1" style="z-index: 1055;"
    data-demo-url="{{ tool.demo | e }}">
    <div class="modal-dialog modal-xl" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">{{tool.name}}</h4><button class="btn-close" type="button" aria-label="Close"
                    data-bs-dismiss="modal"></button>
            </div>
            
            <div class="modal-body" data-demo-loaded="false"></div>
        </div>
        <div class="modal-footer"><button class="btn btn-light" type="button" data-bs-dismiss="modal">Close</button>
        </div>
    </div>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}

<script>
    document.addEventListener('DOMContentLoaded', function () {
        const navbar = document.getElementById('mainNav');
        const headers = document.querySelectorAll('.section-header');

        if (navbar) {
            const navbarHeight = navbar.offsetHeight;
            headers.forEach(header => {
                header.style.top = navbarHeight + 'px';
                header.style.zIndex = '100';
                header.style.scrollMarginTop = navbarHeight + 'px';
            });

            // Handle hash navigation on page load
            if (window.location.hash) {
                setTimeout(() => {
                    const target = document.querySelector(window.location.hash);
                    if (target) {
                        window.scrollTo({
                            top: target.offsetTop - navbarHeight,
                            behavior: 'smooth'
                        });
                    }
                }, 0);
            }
        }

       // Load demo in modal
    document.querySelectorAll('.modal').forEach(modal => {
        modal.addEventListener('show.bs.modal', () => {
            const body = modal.querySelector('.modal-body');
            if (body.dataset.demoLoaded === 'false') {
                const demoUrl = modal.dataset.demoUrl;
                const iframeId = 'iframe-' + modal.id;

                // Add a div on top of all content to show loading message
                const loadingDiv = document.createElement('div');
                loadingDiv.style.position = 'absolute';
                loadingDiv.style.top = '0';
                loadingDiv.style.left = '0';
                loadingDiv.style.width = '100%';
                loadingDiv.style.height = '100%';
                loadingDiv.style.backgroundColor = 'rgb(0, 0, 0)';
                loadingDiv.style.display = 'flex';
                loadingDiv.style.justifyContent = 'center';
                loadingDiv.style.alignItems = 'center';
                loadingDiv.style.zIndex = '10';
                const loadingMsg = document.createElement('p');
                loadingMsg.className = 'text-center';
                loadingMsg.textContent = 'Loading demo...';
                loadingDiv.appendChild(loadingMsg);
                body.style.position = 'relative';
                body.appendChild(loadingDiv);

                // Create iframe
                const iframe = document.createElement('iframe');
                iframe.src = demoUrl + '/iframe';
                iframe.id = iframeId;
                iframe.style.width = '100%';
                iframe.style.height = '400px'; // temporary height
                iframe.style.border = '0';
                iframe.setAttribute('scrolling', 'no');
                iframe.setAttribute('allowfullscreen', 'true');

                body.appendChild(iframe);
                body.dataset.demoLoaded = 'true';

                // Listen for bodySize message from asciinema iframe
                const origin = new URL(demoUrl).origin;
                function onMessage(event) {
                    if (event.origin !== origin || event.source !== iframe.contentWindow) return;
                    if (event.data.type === 'bodySize' && event.data.payload.height) {
                        iframe.style.height = event.data.payload.height + 'px';
                        // Remove loading message
                        body.removeChild(loadingDiv);
                        // Optional: limit modal max height
                        modal.querySelector('.modal-dialog').style.maxHeight = '90vh';
                    }
                }

                window.addEventListener('message', onMessage, false);
            }
        });
    });
});
</script></div>
    </section>
    <footer>
        <div class="container text-center">
            <p class="copyright">Copyright ©&nbsp;Nathan.Woodburn/ 2026</p>
        </div>
    </footer>{{handshake_scripts | safe}}
    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    <script src="/assets/js/script.min.js"></script>
    <script src="/assets/js/grayscale.min.js"></script>
    <script src="/assets/js/hacker.min.js"></script>
</body>

</html>