• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

HOST IO - INTERACTIVE APP SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Host.io API</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            -webkit-tap-highlight-color: transparent;
        }

        body {
            background-color: #000000;
            color: #ffffff;
            min-height: 100vh;
            overflow-x: hidden;
            font-size: 14px;
            -webkit-font-smoothing: antialiased;
        }

        .container {
            max-width: 1600px;
            margin: 0 auto;
            padding: 12px;
        }

        header {
            background: linear-gradient(135deg, #000000 0%, #0a0a0a 100%);
            border-bottom: 1px solid #1a1a1a;
            padding: 10px 0;
            position: sticky;
            top: 0;
            z-index: 100;
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
        }

        .header-content {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 12px;
        }

        .domain-input-container {
            flex-grow: 1;
            min-width: 0;
        }

        .input-group {
            display: flex;
            background-color: #111111;
            border-radius: 8px;
            overflow: hidden;
            border: 1px solid #2a2a2a;
        }

        .input-group:focus-within {
            border-color: #00d4aa;
            box-shadow: 0 0 0 2px rgba(0, 212, 170, 0.2);
        }

        .input-group input {
            flex-grow: 1;
            background: transparent;
            border: none;
            padding: 12px 15px;
            color: #ffffff;
            font-size: 15px;
            outline: none;
            min-width: 0;
        }

        .input-group button {
            background-color: #00d4aa;
            color: #000000;
            border: none;
            padding: 0 16px;
            font-weight: 600;
            font-size: 14px;
            cursor: pointer;
            transition: all 0.2s ease;
            white-space: nowrap;
            flex-shrink: 0;
        }

        .input-group button:hover {
            background-color: #00b894;
        }

        .input-group button:disabled {
            background-color: #444444;
            cursor: not-allowed;
        }

        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(min(100%, 350px), 1fr));
            gap: 14px;
            margin-top: 16px;
        }

        .card {
            background-color: #111111;
            border-radius: 10px;
            border: 1px solid #222222;
            overflow: hidden;
        }

        .card-header {
            padding: 14px 16px;
            border-bottom: 1px solid #222222;
            background-color: rgba(0, 0, 0, 0.3);
        }

        .card-header h2 {
            font-size: 15px;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .card-header i {
            color: #00d4aa;
            font-size: 14px;
        }

        .card-content {
            padding: 16px;
        }

        .data-row {
            display: flex;
            justify-content: space-between;
            padding: 10px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
            flex-wrap: wrap;
            gap: 4px;
        }

        .data-row:last-child {
            border-bottom: none;
        }

        .data-label {
            color: #aaaaaa;
            font-weight: 500;
            font-size: 13px;
            flex-shrink: 0;
        }

        .data-value {
            color: #ffffff;
            font-weight: 400;
            text-align: right;
            max-width: 65%;
            word-break: break-word;
            font-size: 13px;
            flex: 1;
            min-width: 0;
        }

        .data-value a {
            color: #00d4aa;
            text-decoration: none;
            word-break: break-all;
        }

        .data-value a:hover {
            text-decoration: underline;
        }

        .list-item {
            padding: 6px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
            font-size: 13px;
            word-break: break-word;
        }

        .list-item:last-child {
            border-bottom: none;
        }

        .count-badge {
            background-color: #00d4aa;
            color: #000000;
            padding: 3px 8px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: 600;
            margin-left: 5px;
            display: inline-block;
            line-height: 1;
        }

        .loading {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 30px 12px;
            text-align: center;
        }

        .spinner {
            width: 28px;
            height: 28px;
            border: 3px solid rgba(0, 212, 170, 0.2);
            border-top: 3px solid #00d4aa;
            border-radius: 50%;
            animation: spin 1s linear infinite;
            margin-bottom: 12px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .error {
            background-color: rgba(255, 118, 117, 0.1);
            border-left: 3px solid #ff7675;
            padding: 12px;
            margin: 8px 0;
            border-radius: 4px;
            font-size: 13px;
        }

        .related-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
            gap: 10px;
        }

        .related-item {
            background-color: rgba(0, 212, 170, 0.1);
            border: 1px solid rgba(0, 212, 170, 0.2);
            border-radius: 6px;
            padding: 12px;
            font-size: 12px;
            word-break: break-word;
        }

        #initialState {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            padding: 30px 0;
            text-align: left;
            max-width: 800px;
        }

        .example-buttons {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
            gap: 10px;
            width: 100%;
            max-width: 100%;
            margin-top: 10px;
        }

        .example-btn {
            background-color: rgba(0,212,170,0.1);
            border: 1px solid #00d4aa;
            color: #00d4aa;
            padding: 10px 12px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 500;
            font-size: 13px;
            transition: all 0.2s ease;
            text-align: center;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            min-height: 44px;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
            box-sizing: border-box;
        }

        .example-btn:hover {
            background-color: rgba(0,212,170,0.2);
        }

        .status-message {
            padding: 10px 12px;
            margin: 8px 0;
            border-radius: 6px;
            font-size: 13px;
            display: none;
            word-break: break-word;
        }

        .status-success {
            background-color: rgba(0, 212, 170, 0.1);
            border-left: 3px solid #00d4aa;
        }

        .status-error {
            background-color: rgba(255, 118, 117, 0.1);
            border-left: 3px solid #ff7675;
        }

        ::-webkit-scrollbar {
            width: 4px;
            height: 4px;
        }

        ::-webkit-scrollbar-track {
            background: #111111;
        }

        ::-webkit-scrollbar-thumb {
            background: #00d4aa;
            border-radius: 2px;
        }

        @media (max-width: 768px) {
            body {
                font-size: 13px;
            }

            .container {
                padding: 10px;
            }

            header {
                padding: 8px 0;
            }

            .input-group input {
                padding: 10px 12px;
                font-size: 14px;
            }

            .input-group button {
                padding: 0 14px;
                font-size: 13px;
            }

            .dashboard {
                gap: 12px;
                margin-top: 14px;
            }

            .card {
                border-radius: 8px;
            }

            .card-header {
                padding: 12px 14px;
            }

            .card-header h2 {
                font-size: 14px;
            }

            .card-content {
                padding: 14px;
            }

            .data-row {
                padding: 8px 0;
            }

            .data-label {
                font-size: 12px;
            }

            .data-value {
                font-size: 12px;
                max-width: 70%;
            }

            .loading {
                padding: 20px 10px;
            }

            .spinner {
                width: 24px;
                height: 24px;
            }

            #initialState {
                padding: 20px 0;
            }

            .example-buttons {
                grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
                gap: 8px;
            }

            .example-btn {
                font-size: 12px;
                padding: 8px 10px;
                min-height: 40px;
            }

            .related-grid {
                grid-template-columns: repeat(auto-fill, minmax(min(120px, 100%), 1fr));
                gap: 8px;
            }

            .related-item {
                padding: 10px;
                font-size: 11px;
            }
        }

        @media (max-width: 480px) {
            .container {
                padding: 8px;
            }

            .input-group {
                flex-direction: column;
                border-radius: 8px;
                overflow: hidden;
            }

            .input-group input {
                padding: 12px;
                width: 100%;
                border-bottom: 1px solid #2a2a2a;
            }

            .input-group button {
                padding: 12px;
                width: 100%;
            }

            .dashboard {
                gap: 10px;
            }

            .card-header {
                padding: 10px 12px;
            }

            .card-content {
                padding: 12px;
            }

            .data-row {
                flex-direction: column;
                gap: 2px;
                padding: 8px 0;
            }

            .data-label, .data-value {
                text-align: left;
                max-width: 100%;
            }

            .data-value {
                margin-top: 2px;
            }

            .related-grid {
                grid-template-columns: 1fr;
                gap: 8px;
            }

            .example-buttons {
                grid-template-columns: repeat(2, 1fr);
                gap: 8px;
            }

            .example-btn {
                font-size: 11px;
                padding: 8px 6px;
                min-height: 38px;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                max-width: 100%;
            }
        }

        @media (max-width: 360px) {
            .example-buttons {
                grid-template-columns: 1fr;
                gap: 6px;
            }

            .example-btn {
                font-size: 12px;
                padding: 10px 8px;
                min-height: 42px;
            }

            .container {
                padding: 6px;
            }
        }

        html, body {
            max-width: 100%;
            overflow-x: hidden;
        }

        button,
        .example-btn,
        .input-group button {
            min-height: 44px;
            min-width: 44px;
        }

        .dashboard, .card {
            will-change: transform;
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <div class="domain-input-container">
                <div class="input-group">
                    <input type="text" id="domainInput" placeholder="Enter domain (e.g., google.com, bbc.co.uk)" value="example.com">
                    <button id="fetchBtn">
                        <i class="fas fa-search"></i> Analyse Domain
                    </button>
                </div>
            </div>
        </div>
    </header>

    <main class="container">
        <div id="statusContainer" class="status-message"></div>

        <div id="loading" class="loading" style="display: none;">
            <div class="spinner"></div>
            <h3>Fetching domain intelligence data...</h3>
        </div>

        <div id="dashboard" class="dashboard" style="display: none;">
            <!-- Web Metadata Card -->
            <div class="card">
                <div class="card-header">
                    <h2><i class="fas fa-browser"></i> Web Metadata</h2>
                </div>
                <div class="card-content" id="webData">
                    <div class="loading">
                        <div class="spinner" style="width: 20px; height: 20px;"></div>
                    </div>
                </div>
            </div>

            <!-- DNS Records Card -->
            <div class="card">
                <div class="card-header">
                    <h2><i class="fas fa-server"></i> DNS Records</h2>
                </div>
                <div class="card-content" id="dnsData">
                    <div class="loading">
                        <div class="spinner" style="width: 20px; height: 20px;"></div>
                    </div>
                </div>
            </div>

            <!-- IP Information Card -->
            <div class="card">
                <div class="card-header">
                    <h2><i class="fas fa-network-wired"></i> IP Information</h2>
                </div>
                <div class="card-content" id="ipData">
                    <div class="loading">
                        <div class="spinner" style="width: 20px; height: 20px;"></div>
                    </div>
                </div>
            </div>

            <!-- Related Domains Card -->
            <div class="card">
                <div class="card-header">
                    <h2><i class="fas fa-sitemap"></i> Related Domains</h2>
                </div>
                <div class="card-content" id="relatedData">
                    <div class="loading">
                        <div class="spinner" style="width: 20px; height: 20px;"></div>
                    </div>
                </div>
            </div>

            <!-- Extended Data Card -->
            <div class="card" id="extendedCard">
                <div class="card-header">
                    <h2><i class="fas fa-chart-network"></i> Extended Intelligence</h2>
                </div>
                <div class="card-content" id="extendedData">
                    <div class="loading">
                        <div class="spinner" style="width: 20px; height: 20px;"></div>
                    </div>
                </div>
            </div>
        </div>

        <div id="initialState" class="loading">
            <h3 style="margin-bottom: 10px; text-align: left;">Enter a domain to analyse</h3>
            <p style="margin-bottom: 20px; color: #aaaaaa; text-align: left;">Obtain comprehensive domain intelligence including web metadata, DNS records, IP information, and related domains.</p>
            <div class="example-buttons">
                <button class="example-btn" data-domain="google.com">google.com</button>
                <button class="example-btn" data-domain="facebook.com">facebook.com</button>
                <button class="example-btn" data-domain="github.com">github.com</button>
                <button class="example-btn" data-domain="bbc.co.uk">bbc.co.uk</button>
                <button class="example-btn" data-domain="gov.uk">gov.uk</button>
            </div>
        </div>
    </main>

    <script>
        const domainInput = document.getElementById('domainInput');
        const fetchBtn = document.getElementById('fetchBtn');
        const loading = document.getElementById('loading');
        const statusContainer = document.getElementById('statusContainer');
        const dashboard = document.getElementById('dashboard');
        const initialState = document.getElementById('initialState');
        const exampleBtns = document.querySelectorAll('.example-btn');

        const webData = document.getElementById('webData');
        const dnsData = document.getElementById('dnsData');
        const ipData = document.getElementById('ipData');
        const relatedData = document.getElementById('relatedData');
        const extendedData = document.getElementById('extendedData');
        const extendedCard = document.getElementById('extendedCard');

        const API_BASE = 'https://host.io';
        const TOKEN_PLACEHOLDER = '{{HOST_TOKEN}}';

        function formatDate(dateString) {
            if (!dateString) return 'N/A';
            const date = new Date(dateString);
            return date.toLocaleDateString('en-GB') + ' ' + date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
        }

        function showStatus(message, type = 'success') {
            statusContainer.textContent = message;
            statusContainer.className = `status-message status-${type}`;
            statusContainer.style.display = 'block';

            if (type === 'success') {
                setTimeout(() => {
                    statusContainer.style.display = 'none';
                }, 3000);
            }
        }

        function createDataRow(label, value) {
            const row = document.createElement('div');
            row.className = 'data-row';

            const labelSpan = document.createElement('span');
            labelSpan.className = 'data-label';
            labelSpan.textContent = label;

            const valueSpan = document.createElement('span');
            valueSpan.className = 'data-value';

            if (Array.isArray(value)) {
                if (value.length === 0) {
                    valueSpan.textContent = 'None';
                } else {
                    const fragment = document.createDocumentFragment();
                    value.forEach((item, index) => {
                        const div = document.createElement('div');
                        div.className = 'list-item';
                        div.textContent = item;
                        fragment.appendChild(div);
                    });
                    valueSpan.appendChild(fragment);
                }
            } else if (typeof value === 'object' && value !== null) {
                const fragment = document.createDocumentFragment();
                Object.entries(value).forEach(([key, val]) => {
                    const div = document.createElement('div');
                    div.className = 'list-item';
                    div.innerHTML = `<strong>${key}:</strong> ${val}`;
                    fragment.appendChild(div);
                });
                valueSpan.appendChild(fragment);
            } else {
                valueSpan.textContent = value || 'N/A';
            }

            row.appendChild(labelSpan);
            row.appendChild(valueSpan);
            return row;
        }

        async function fetchHostIOData(endpoint) {
            const url = `${API_BASE}${endpoint}`;
            const headers = {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Authorization': `Bearer ${TOKEN_PLACEHOLDER}`
            };

            try {
                const response = await window.proxyFetch(url, {
                    method: 'GET',
                    headers: headers
                });

                if (!response.ok) {
                    if (response.status === 404) {
                        const errorData = await response.json();
                        throw new Error(errorData.error || 'Domain not found');
                    } else if (response.status === 400) {
                        throw new Error('Authentication failed');
                    } else {
                        throw new Error(`HTTP ${response.status}`);
                    }
                }

                return await response.json();
            } catch (error) {
                throw new Error(`Failed to fetch data: ${error.message}`);
            }
        }

        function renderWebData(data) {
            if (!data || data.error) {
                webData.innerHTML = '<div class="error">No web metadata available</div>';
                return;
            }

            const fragment = document.createDocumentFragment();

            fragment.appendChild(createDataRow('Domain', data.domain));
            if (data.rank) fragment.appendChild(createDataRow('Global Rank', `#${data.rank}`));
            fragment.appendChild(createDataRow('URL', data.url));
            fragment.appendChild(createDataRow('IP Address', data.ip));
            fragment.appendChild(createDataRow('Last Scraped', formatDate(data.date)));
            if (data.length) fragment.appendChild(createDataRow('Content Size', `${Math.round(data.length / 1024)} KB`));
            fragment.appendChild(createDataRow('Encoding', data.encoding));
            fragment.appendChild(createDataRow('Title', data.title));
            fragment.appendChild(createDataRow('Description', data.description));
            if (data.copyright) fragment.appendChild(createDataRow('Copyright', data.copyright));
            if (data.server) fragment.appendChild(createDataRow('Server', data.server));
            if (data.links && data.links.length > 0) {
                fragment.appendChild(createDataRow('External Links', data.links));
            }

            webData.innerHTML = '';
            webData.appendChild(fragment);
        }

        function renderDNSData(data) {
            if (!data || data.error) {
                dnsData.innerHTML = '<div class="error">No DNS records available</div>';
                return;
            }

            const fragment = document.createDocumentFragment();

            fragment.appendChild(createDataRow('Domain', data.domain));

            if (data.a && data.a.length > 0) {
                fragment.appendChild(createDataRow('A Records (IPv4)', data.a));
            }

            if (data.aaaa && data.aaaa.length > 0) {
                fragment.appendChild(createDataRow('AAAA Records (IPv6)', data.aaaa));
            }

            if (data.mx && data.mx.length > 0) {
                fragment.appendChild(createDataRow('MX Records (Mail)', data.mx));
            }

            if (data.ns && data.ns.length > 0) {
                fragment.appendChild(createDataRow('NS Records (Nameservers)', data.ns));
            }

            dnsData.innerHTML = '';
            dnsData.appendChild(fragment);
        }

        function renderIPData(ipinfo) {
            if (!ipinfo || Object.keys(ipinfo).length === 0) {
                ipData.innerHTML = '<div class="error">No IP information available</div>';
                return;
            }

            const fragment = document.createDocumentFragment();

            for (const [ip, info] of Object.entries(ipinfo)) {
                const ipSection = document.createElement('div');
                ipSection.style.marginBottom = '12px';
                ipSection.style.paddingBottom = '10px';
                ipSection.style.borderBottom = '1px solid #222222';

                const ipHeader = document.createElement('h4');
                ipHeader.style.color = '#00d4aa';
                ipHeader.style.marginBottom = '8px';
                ipHeader.style.fontSize = '13px';
                ipHeader.textContent = ip;
                ipSection.appendChild(ipHeader);

                if (info.city) ipSection.appendChild(createDataRow('City', info.city));
                if (info.region) ipSection.appendChild(createDataRow('Region', info.region));
                if (info.country) ipSection.appendChild(createDataRow('Country', info.country));
                if (info.loc) ipSection.appendChild(createDataRow('Location', info.loc));
                if (info.timezone) ipSection.appendChild(createDataRow('Timezone', info.timezone));

                if (info.asn) {
                    const asnSection = document.createElement('div');
                    asnSection.style.marginTop = '8px';
                    asnSection.style.padding = '8px';
                    asnSection.style.backgroundColor = 'rgba(0,0,0,0.3)';
                    asnSection.style.borderRadius = '4px';

                    const asnHeader = document.createElement('h5');
                    asnHeader.style.color = '#aaaaaa';
                    asnHeader.style.marginBottom = '6px';
                    asnHeader.style.fontSize = '12px';
                    asnHeader.textContent = 'ASN Information';
                    asnSection.appendChild(asnHeader);

                    if (info.asn.asn) asnSection.appendChild(createDataRow('ASN', info.asn.asn));
                    if (info.asn.name) asnSection.appendChild(createDataRow('Name', info.asn.name));
                    if (info.asn.domain) asnSection.appendChild(createDataRow('Domain', info.asn.domain));
                    if (info.asn.route) asnSection.appendChild(createDataRow('Route', info.asn.route));

                    ipSection.appendChild(asnSection);
                }

                fragment.appendChild(ipSection);
            }

            ipData.innerHTML = '';
            ipData.appendChild(fragment);
        }

        function renderRelatedData(data) {
            if (!data || Object.keys(data).length === 0) {
                relatedData.innerHTML = '<div class="error">No related domain data available</div>';
                return;
            }

            const fragment = document.createDocumentFragment();

            for (const [field, items] of Object.entries(data)) {
                if (items && items.length > 0) {
                    const fieldHeader = document.createElement('h4');
                    fieldHeader.style.color = '#00d4aa';
                    fieldHeader.style.margin = '10px 0 8px 0';
                    fieldHeader.style.fontSize = '13px';
                    fieldHeader.style.textTransform = 'capitalize';
                    fieldHeader.textContent = field.replace('backlinks', 'Back Links');
                    fragment.appendChild(fieldHeader);

                    items.forEach(item => {
                        const itemDiv = document.createElement('div');
                        itemDiv.className = 'related-item';

                        const valueDiv = document.createElement('div');
                        valueDiv.style.fontWeight = '600';
                        valueDiv.style.marginBottom = '4px';
                        valueDiv.textContent = item.value;

                        const countDiv = document.createElement('div');
                        countDiv.textContent = 'Related: ';

                        const badge = document.createElement('span');
                        badge.className = 'count-badge';
                        badge.textContent = item.count.toLocaleString();

                        countDiv.appendChild(badge);
                        itemDiv.appendChild(valueDiv);
                        itemDiv.appendChild(countDiv);
                        fragment.appendChild(itemDiv);
                    });
                }
            }

            relatedData.innerHTML = '';
            const gridContainer = document.createElement('div');
            gridContainer.className = 'related-grid';
            gridContainer.appendChild(fragment);
            relatedData.appendChild(gridContainer);
        }

        function renderExtendedData(fullData) {
            if (!fullData) {
                extendedData.innerHTML = '<div class="error">No extended data available</div>';
                return;
            }

            const fragment = document.createDocumentFragment();

            const coverageDiv = document.createElement('div');
            coverageDiv.style.display = 'grid';
            coverageDiv.style.gridTemplateColumns = 'repeat(auto-fill, minmax(180px, 1fr))';
            coverageDiv.style.gap = '10px';

            const dataPoints = [
                { name: 'Web Metadata', has: !!fullData.web },
                { name: 'DNS Records', has: !!fullData.dns },
                { name: 'IP Information', has: !!fullData.ipinfo && Object.keys(fullData.ipinfo).length > 0 },
                { name: 'Related Domains', has: !!fullData.related && Object.keys(fullData.related).length > 0 }
            ];

            dataPoints.forEach(point => {
                const pointDiv = document.createElement('div');
                pointDiv.style.padding = '8px';
                pointDiv.style.border = '1px solid #222222';
                pointDiv.style.borderRadius = '4px';
                pointDiv.style.fontSize = '12px';

                const nameSpan = document.createElement('span');
                nameSpan.style.color = '#aaaaaa';
                nameSpan.textContent = point.name;

                const statusSpan = document.createElement('span');
                statusSpan.style.float = 'right';
                statusSpan.style.color = point.has ? '#00d4aa' : '#ff7675';
                statusSpan.innerHTML = `<i class="fas fa-${point.has ? 'check' : 'times'}"></i>`;

                pointDiv.appendChild(nameSpan);
                pointDiv.appendChild(statusSpan);
                coverageDiv.appendChild(pointDiv);
            });

            fragment.appendChild(coverageDiv);

            extendedData.innerHTML = '';
            extendedData.appendChild(fragment);
        }

        async function fetchDomainData(domain) {
            if (!domain || !domain.trim()) {
                showStatus('Please enter a valid domain', 'error');
                return;
            }

            let cleanDomain = domain.trim().toLowerCase();
            if (cleanDomain.startsWith('http://')) cleanDomain = cleanDomain.replace('http://', '');
            if (cleanDomain.startsWith('https://')) cleanDomain = cleanDomain.replace('https://', '');
            if (cleanDomain.startsWith('www.')) cleanDomain = cleanDomain.replace('www.', '');
            cleanDomain = cleanDomain.replace(/\/$/, '');

            loading.style.display = 'flex';
            dashboard.style.display = 'none';
            initialState.style.display = 'none';

            fetchBtn.disabled = true;
            fetchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Analysing...';

            try {
                const fullData = await fetchHostIOData(`/api/full/${cleanDomain}`);

                renderWebData(fullData.web);
                renderDNSData(fullData.dns);
                renderIPData(fullData.ipinfo);
                renderRelatedData(fullData.related);
                renderExtendedData(fullData);

                loading.style.display = 'none';
                dashboard.style.display = 'grid';

                document.title = `${cleanDomain} - Domain Intelligence`;
                showStatus(`Successfully analysed ${cleanDomain}`);

            } catch (error) {
                console.error('Error:', error);
                showStatus(error.message, 'error');

                loading.style.display = 'none';
                initialState.style.display = 'flex';
            } finally {
                fetchBtn.disabled = false;
                fetchBtn.innerHTML = '<i class="fas fa-search"></i> Analyse Domain';
            }
        }

        fetchBtn.addEventListener('click', () => {
            fetchDomainData(domainInput.value);
        });

        domainInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                fetchDomainData(domainInput.value);
            }
        });

        exampleBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                const domain = btn.getAttribute('data-domain');
                domainInput.value = domain;
                fetchDomainData(domain);
            });
        });

        window.addEventListener('DOMContentLoaded', () => {
            domainInput.focus();
        });

        function adjustLayout() {
            if (window.innerWidth <= 768) {
                extendedCard.style.gridColumn = 'span 1';
            } else {
                extendedCard.style.gridColumn = 'span 2';
            }
        }

        adjustLayout();

        window.addEventListener('resize', adjustLayout);
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.