Files
Chris Tsang 1adde15f42 Fix the macOS download link 404ing
publicDownloadUrl built the disk image URL from manifest.version, but the
version carries a build suffix the release tag does not: version
"1.0.0-alpha.3.app.59" lives under tag "1.0.0-alpha.3". Every macOS
visitor was handed a 404.

Derive it from the updater URL instead, rewriting only the filename so
the tag comes from the release directory the manifest points at.
2026-08-27 00:00:13 +01:00

479 lines
20 KiB
HTML
Generated

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>VTracer</title>
<link rel="apple-touch-icon" sizes="192x192" href="./assets/apple-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="./assets/apple-icon.png">
<!-- UIkit CSS -->
<link rel="stylesheet" href="./uikit/css/uikit.min.css" />
<!-- UIkit JS -->
<script src="./uikit/js/uikit.min.js"></script>
<script src="./uikit/js/uikit-icons.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XPL1JGMDSC"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XPL1JGMDSC');
</script>
<style>
html, body {
width: 100%;
height: 100%;
}
.uk-dark body,
.uk-dark .container {
background: #fff;
}
.uk-dark #vc-logo-light {
display: none;
}
.uk-light #vc-logo {
display: none;
}
.uk-light body,
.uk-light .container,
.uk-light .uk-modal-dialog {
background: #2e2e2e;
}
@media screen and (prefers-color-scheme: dark) {
html {
background: #2e2e2e;
}
}
#droptext {
background: #00275D;
border: 2px dashed #CC972E;
}
#droptext.hovering {
border: 2px dashed #fff;
}
.uk-navbar-right.uk-padding-small {
padding-top: 0;
padding-bottom: 0;
}
#export,
.uk-button-group .uk-button.selected {
color: #fff;
background: #00275D;
border-color: #CC972E;
}
.uk-dark #export,
.uk-dark .uk-button-group .uk-button.selected {
border-width: 2px;
}
.uk-dark #droptext {
border-width: 4px;
}
.uk-button-group > button {
padding-left: 10px;
padding-right: 10px;
}
@media only screen and (min-width: 1280px) {
.uk-button-group > button {
padding-left: 25px;
padding-right: 25px;
}
}
#canvas-container {
position: relative;
height: max-content;
}
#svg, #frame {
position: absolute;
width: 100%;
margin-bottom: 50px;
}
#svg > path:hover {
stroke: #ff0;
}
#droptext {
height: 480px;
color: #CC972E;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
background: #00275D;
border-color: #CC972E;
}
input[type=range]::-moz-range-thumb {
background: #00275D;
border-color: #CC972E;
}
input[type=range]::-ms-thumb {
background: #00275D;
border-color: #CC972E;
}
#drop, #gallery, #options {
border-top: 1px solid #686868;
}
#gallery {
padding-bottom: 0;
}
.galleryitem {
cursor: pointer;
padding: 2px;
margin: 1px;
}
.galleryitem:hover {
padding: 0px;
margin: 0px;
border: 2px solid #CC972E;
}
.galleryitem img {
max-height: 120px;
}
.uk-text-meta {
font-size: 12px;
}
.desktop-banner {
border: 1px solid rgba(204, 151, 46, 0.55);
background: rgba(204, 151, 46, 0.12);
gap: 8px 16px;
padding-top: 8px;
padding-bottom: 8px;
}
.desktop-banner-title {
margin: 0;
font-weight: 700;
}
.desktop-banner-copy {
margin: 4px 0 0 0;
}
.desktop-banner .uk-button {
white-space: nowrap;
}
.uk-light .desktop-banner {
border-color: rgba(204, 151, 46, 0.7);
background: rgba(204, 151, 46, 0.16);
}
@media only screen and (max-width: 640px) {
.desktop-banner {
text-align: center;
}
.desktop-banner .uk-button {
width: 100%;
}
}
</style>
</head>
<body class="uk-dark">
<input type="file" id="imageInput" accept="image/*" style="display: none;">
<div class="container uk-padding" uk-grid>
<div class="uk-width-1-1 uk-navbar-container uk-navbar-transparent" uk-navbar>
<div class="uk-navbar-left uk-text-large uk-text-bolder">
<a id="vc-logo" class="uk-logo" href="//www.visioncortex.org"><img style="height: 50px; margin: -30px 0 -25px 0;" src="./assets/visioncortex-logo.svg"></a>
<a id="vc-logo-light" class="uk-logo" href="//www.visioncortex.org"><img style="height: 50px; margin: -30px 0 -25px 0;" src="./assets/visioncortex-logo-light.svg"></a>
&nbsp;
/ VTracer
</div>
<div class="uk-navbar-right uk-padding-small">
<a class="uk-button uk-button-default" href="//github.com/visioncortex/vtracer">GitHub</a>
&nbsp;&nbsp;
<a id="export" class="uk-button uk-button-primary">Download as SVG</a>
</div>
</div>
<div class="uk-width-1-1">
<div class="desktop-banner uk-padding-small uk-flex uk-flex-middle uk-flex-between uk-flex-wrap">
<div class="uk-margin-small-right">
<p id="desktop-banner-copy" class="desktop-banner-title">Try the latest VTracer Desktop App for native speed, curve simplification, and higher tracing quality.</p>
</div>
<a id="desktop-download" class="uk-button uk-button-primary" href="https://github.com/visioncortex/vtracer/releases/latest" rel="noopener">
Download Desktop App
</a>
</div>
</div>
<div id="progressregion" class="uk-width-1-1 uk-padding-small" style="display: none;">
<progress id="progressbar" class="uk-progress uk-align-right" value="0" max="100" style="width: 98%;"></progress>
</div>
<div class="uk-width-3-4">
<div id="drop" class="uk-padding uk-flex uk-flex-center">
<div id="canvas-container" class="uk-width-1-1">
<div id="droptext" class="uk-flex uk-flex-middle uk-flex-center">
<p>Drag an image here, Cmd-V to paste or <a href="#" id="imageSelect">Select file</a></p>
</div>
<canvas id="frame"></canvas>
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
</div>
</div>
<div id="gallery" class="uk-padding">
<div class="uk-inline" uk-slider>
<ul id="galleryslider" class="uk-slider-items uk-child-width-1-6 uk-grid uk-grid-small">
</ul>
<a class="uk-position-center-left uk-position-small uk-hidden-hover" href="#" uk-slidenav-previous uk-slider-item="previous"></a>
<a class="uk-position-center-right uk-position-small uk-hidden-hover" href="#" uk-slidenav-next uk-slider-item="next"></a>
</div>
<a class="uk-align-right uk-text-meta" style="margin:-5px;" uk-toggle="target: #credits-modal">Photo Credits</a>
</div>
</div>
<div id="options" class="uk-width-1-4 uk-height-1-1 uk-grid-small uk-flex uk-flex-center" uk-grid>
<div class="uk-width-1-1 uk-flex uk-flex-right">
&nbsp;
</div>
<p></p>
<div class="uk-width-1-1 uk-flex uk-flex-right">
<div class="uk-text-bold" uk-tooltip="pos:left; title: Algorithm for segmentation and grouping of pixel clusters">
Clustering
</div>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-right">
<div class="uk-button-group">
<button id="clustering-binary" class="uk-button uk-button-default" uk-tooltip="Black & White (Binary Image)">B/W</button>
<button id="clustering-color" class="selected uk-button uk-button-default" uk-tooltip="True Color Image">Color</button>
</div>
</div>
<div class="clustering-color-options uk-width-1-1 uk-flex uk-flex-right">
<div class="uk-button-group">
<button id="clustering-cutout" class="uk-button uk-button-default uk-button-small" uk-tooltip="Shapes disjoint with others">Cutout</button>
<button id="clustering-stacked" class="selected uk-button uk-button-default uk-button-small" uk-tooltip="Stack shapes on top of another">Stacked</button>
</div>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Discard patches small than X px in size">
Filter Speckle <span class="uk-text-meta">(Cleaner)</span>
</div>
</div>
<div id="filterspecklevalue" class="uk-width-1-6">
4
</div>
<div class="uk-width-5-6">
<input id="filterspeckle" class="uk-range" type="range" min="0" max="128" step="1" value="4">
</div>
<div class="clustering-color-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Number of significant bits to use in a RGB channel">
Color Precision <span class="uk-text-meta">(More accurate)</span>
</div>
</div>
<div id="colorprecisionvalue" class="clustering-color-options uk-width-1-6">
6
</div>
<div class="clustering-color-options uk-width-5-6">
<input id="colorprecision" class="uk-range" type="range" min="1" max="8" step="1" value="6">
</div>
<div class="clustering-color-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Color difference between gradient layers">
Gradient Step <span class="uk-text-meta">(Less layers)</span>
</div>
</div>
<div id="layerdifferencevalue" class="clustering-color-options uk-width-1-6">
16
</div>
<div class="clustering-color-options uk-width-5-6">
<input id="layerdifference" class="uk-range" type="range" min="0" max="128" step="1" value="16">
</div>
<div class="uk-width-1-1 uk-flex uk-flex-right">
<div class="uk-text-bold" uk-tooltip="pos: left; title: Algorithm for converting clusters to shapes">
Curve Fitting
</div>
</div>
<div class="uk-width-1-1 uk-flex uk-flex-right">
<div class="uk-button-group">
<button id="none" class="uk-button uk-button-default" uk-tooltip="Exact cluster boundary">PIXEL</button>
<button id="polygon" class="uk-button uk-button-default" uk-tooltip="Simplify to Polygon">Polygon</button>
<button id="spline" class="selected uk-button uk-button-default" uk-tooltip="Smooth and Curve-fit">Spline</button>
</div>
</div>
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Minimum Momentary Angle (in degrees) to be considered a corner (to be kept after smoothing)">
Corner Threshold <span class="uk-text-meta">(Smoother)</span>
</div>
</div>
<div id="cornervalue" class="spline-options uk-width-1-6">
60
</div>
<div class="spline-options uk-width-5-6">
<input id="corner" class="uk-range" type="range" min="0" max="180" step="1" value="60">
</div>
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Perform Iterative Subdivide Smooth until all segments are shorter than this length">
Segment Length <span class="uk-text-meta">(More coarse)</span>
</div>
</div>
<div id="lengthvalue" class="spline-options uk-width-1-6">
4
</div>
<div class="spline-options uk-width-5-6">
<input id="length" class="uk-range" type="range" min="3.5" max="10" step="0.5" value="4">
</div>
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Minimum Angle Displacement (in degrees) to be considered a cutting point between curves">
Splice Threshold <span class="uk-text-meta">(Less accurate)</span>
</div>
</div>
<div id="splicevalue" class="spline-options uk-width-1-6">
45
</div>
<div class="spline-options uk-width-5-6">
<input id="splice" class="uk-range" type="range" min="0" max="180" step="1" value="45">
</div>
<div style="display: none">
<div class="spline-options uk-width-1-1 uk-flex uk-flex-right">
<div uk-tooltip="pos: left; title: Number of decimal places used in svg path string">
Path Precision <span class="uk-text-meta">(More digits)</span>
</div>
</div>
<div id="pathprecisionvalue" class="spline-options uk-width-1-6">
8
</div>
<div class="spline-options uk-width-5-6">
<input id="pathprecision" class="uk-range" type="range" min="0" max="16" step="1" value="8">
</div>
</div>
</div>
</div>
<div id="credits-modal" uk-modal>
<div id="credits-modal-content" class="uk-modal-dialog uk-modal-body">
<p>Image Credits</p>
</div>
</div>
<script>
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.remove('uk-dark');
document.body.classList.add('uk-light');
}
(function() {
var releasesUrl = 'https://github.com/visioncortex/vtracer/releases/latest';
var manifestUrl = 'https://raw.githubusercontent.com/visioncortex/vtracer/desktop-updates/latest.json';
var downloadLink = document.getElementById('desktop-download');
var bannerCopy = document.getElementById('desktop-banner-copy');
function detectPlatform() {
var platform = navigator.userAgentData && navigator.userAgentData.platform
? navigator.userAgentData.platform
: navigator.platform || '';
var userAgent = navigator.userAgent || '';
var platformText = platform + ' ' + userAgent;
if (/Windows/i.test(platformText)) {
return {
label: 'Windows',
manifestKeys: ['windows-x86_64']
};
}
if (/Mac|Darwin/i.test(platformText) && !/iPhone|iPad|iPod/i.test(userAgent)) {
return {
label: 'macOS',
manifestKeys: ['macos-universal', 'darwin-universal', 'darwin-aarch64', 'darwin-x86_64']
};
}
if (/Linux|X11/i.test(platformText) && !/Android/i.test(userAgent)) {
return {
label: 'Linux',
manifestKeys: ['linux-x86_64']
};
}
return {
label: '',
manifestKeys: []
};
}
function setFallback(platform) {
downloadLink.href = releasesUrl;
downloadLink.textContent = platform.label ? 'Download for ' + platform.label : 'View latest downloads';
bannerCopy.textContent = platform.label
? 'Try the latest VTracer Desktop App for ' + platform.label + ' with native speed, curve simplification, and higher tracing quality.'
: 'Try the latest VTracer Desktop App for native speed, curve simplification, and higher tracing quality.';
}
function findPlatformDownload(platforms, platform) {
var keys = platform.manifestKeys || [];
for (var i = 0; i < keys.length; i++) {
if (platforms[keys[i]] && platforms[keys[i]].url) {
return platforms[keys[i]].url;
}
}
return '';
}
function publicDownloadUrl(platform, updaterUrl) {
if (platform.label !== 'macOS' || !updaterUrl) {
return updaterUrl;
}
// The manifest points at Tauri's updater bundle; a person wants the
// disk image sitting beside it. Rewrite only the filename, so the
// release tag comes from the URL we were handed: manifest.version
// carries a build suffix the tag does not, and building the URL from
// it 404s (version '1.0.0-alpha.3.app.59' lives under tag
// '1.0.0-alpha.3').
return updaterUrl.replace(/\.app\.tar\.gz$/i, '.dmg');
}
var platform = detectPlatform();
setFallback(platform);
downloadLink.addEventListener('click', function() {
if (typeof gtag !== 'function') {
return;
}
gtag('event', 'desktop_download_click', {
platform: platform.label || 'unknown',
source: 'docs_banner',
download_url: downloadLink.href,
transport_type: 'beacon'
});
});
if (!window.fetch) {
return;
}
fetch(manifestUrl)
.then(function(response) {
if (!response.ok) {
throw new Error('Desktop manifest lookup failed');
}
return response.json();
})
.then(function(manifest) {
var updaterUrl = findPlatformDownload(manifest.platforms || {}, platform);
var downloadUrl = publicDownloadUrl(platform, updaterUrl);
if (!downloadUrl) {
return;
}
downloadLink.href = downloadUrl;
downloadLink.textContent = 'Download for ' + platform.label;
bannerCopy.textContent = 'Try the latest VTracer Desktop App for ' + platform.label + ' with native speed, curve simplification, and higher tracing quality.';
})
.catch(function() {
setFallback(platform);
});
})();
</script>
<script src="./bootstrap.js"></script>
</body>
</html>