diff --git a/404.html b/404.html index d173cb6..f8b9386 100644 --- a/404.html +++ b/404.html @@ -3,6 +3,10 @@ + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index f7dcd80..35b8514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Pinned the immutable `secure-metadata v0.1.1` browser Release artifact as a same-origin dependency with exact provenance and SHA-256 release-gate coverage. - Added per-file and aggregate compression metrics that distinguish byte savings from larger generated results. - Added production crawler discovery files, canonical URLs, page-specific Open Graph metadata, SEO regression coverage, and search-engine submission guidance for securetools.app. +- Added production favicon, Apple touch, and same-origin social preview assets with Open Graph and Twitter/X card metadata. - Added per-image output dimension/pixel checks and a 200-megapixel aggregate resize-output workload limit. - Added JPEG, PNG, and WebP input/output, lossy quality controls for JPEG/WebP, deterministic white JPEG transparency, metadata-stripping canvas re-encoding, collision-safe Unicode names, and ZIP batch output. - Added per-file, queue, dimension, decoded-pixel, and 200-megapixel aggregate-work protections with recoverable errors. diff --git a/about/index.html b/about/index.html index 2e88f15..c847126 100644 --- a/about/index.html +++ b/about/index.html @@ -3,6 +3,10 @@ + + + + @@ -12,6 +16,15 @@ + + + + + + + + + About — Secure Tools diff --git a/assets/icons/apple-touch-icon.png b/assets/icons/apple-touch-icon.png new file mode 100644 index 0000000..fadc7eb Binary files /dev/null and b/assets/icons/apple-touch-icon.png differ diff --git a/assets/icons/favicon-16x16.png b/assets/icons/favicon-16x16.png new file mode 100644 index 0000000..23db1d9 Binary files /dev/null and b/assets/icons/favicon-16x16.png differ diff --git a/assets/icons/favicon-32x32.png b/assets/icons/favicon-32x32.png new file mode 100644 index 0000000..6245550 Binary files /dev/null and b/assets/icons/favicon-32x32.png differ diff --git a/assets/icons/favicon.ico b/assets/icons/favicon.ico new file mode 100644 index 0000000..c5ebf6c Binary files /dev/null and b/assets/icons/favicon.ico differ diff --git a/assets/images/og-image.png b/assets/images/og-image.png new file mode 100644 index 0000000..da0ffe7 Binary files /dev/null and b/assets/images/og-image.png differ diff --git a/docs/seo.md b/docs/seo.md index 9167d96..fef0401 100644 --- a/docs/seo.md +++ b/docs/seo.md @@ -19,10 +19,11 @@ Every indexable page has: - one HTTPS canonical URL on `securetools.app` using the directory route's trailing slash; - one non-empty, page-specific title and meta description; -- `og:type=website`, `og:site_name=Secure Tools`, page-specific Open Graph title and description, and an `og:url` equal to the canonical URL; +- `og:type=website`, `og:site_name=Secure Tools`, page-specific Open Graph title and description, an `og:url` equal to the canonical URL, and the reviewed same-origin 1200 × 630 share image; +- a Twitter/X `summary_large_image` card that reuses the page title, description, and share image; - no accidental `noindex` or `nofollow` directive. -No `og:image` is declared because the repository does not yet contain a reviewed production share image. Twitter/X card tags are omitted because they would duplicate the current Open Graph fields without an image-specific presentation. +The shared preview is served statically from `https://securetools.app/assets/images/og-image.png`. Favicon and Apple touch icon files are also same-origin assets under `/assets/icons/`; no remote image service, runtime generator, manifest, service worker, or install behavior is involved. ## Language and structured-data decisions diff --git a/docs/v2-release-qa.md b/docs/v2-release-qa.md index 267cf43..a3fcc50 100644 --- a/docs/v2-release-qa.md +++ b/docs/v2-release-qa.md @@ -133,4 +133,17 @@ The user completed real local-browser release QA on 2026-08-27 in Chrome 151.0.7 The Metadata workflows manually covered Privacy Clean, Customize/custom cleaning, metadata removal, source/action UX, and the decoded summary/details flow. The triggering JPEG carried EXIF Orientation=6. Before the dependency patch, removing the complete EXIF APP1 segment could discard rendering-critical Orientation and make the cleaned image display rotated. With secure-metadata v0.1.1, one valid unambiguous IFD0 Orientation value from 1 through 8 is preserved, other targeted EXIF/GPS metadata is removed, and pixel data is neither decoded nor re-encoded. The user manually verified the corrected behavior, so the Orientation blocker is **RESOLVED / PASS**. +## Branding and social preview manual QA + +The user completed real-browser validation for PR #66 on 2026-08-28. This evidence covers the favicon and social-preview polish added after the v2.0.0-rc.1 pre-release was published. + +| Reported check | Result | +| --- | --- | +| Homepage favicon | PASS | +| Direct tool URL favicon | PASS | +| Light and Dark theme regression | PASS | +| New browser console errors | PASS — none observed | +| Broken static asset requests | PASS — none observed | +| Unexpected external network requests | PASS — none observed | + The evidence above records only the test areas and outcomes explicitly reported by the user; it does not invent unreported sub-test detail. Release-required interactive checks are complete, while the historical automated browser attempts remain **BLOCKED**. diff --git a/index.html b/index.html index c7d9508..7e77573 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,10 @@ + + + + @@ -11,6 +15,15 @@ + + + + + + + + + Secure Tools — Private tools for everyday files diff --git a/js/i18n.js b/js/i18n.js index 6a4e14a..071f446 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -92,6 +92,8 @@ const translateDocument = () => { document.querySelector('meta[name="description"]')?.setAttribute("content", t(`metadata.${pageKey}.description`)); document.querySelector('meta[property="og:title"]')?.setAttribute("content", t(`metadata.${pageKey}.title`)); document.querySelector('meta[property="og:description"]')?.setAttribute("content", t(`metadata.${pageKey}.description`)); + document.querySelector('meta[name="twitter:title"]')?.setAttribute("content", t(`metadata.${pageKey}.title`)); + document.querySelector('meta[name="twitter:description"]')?.setAttribute("content", t(`metadata.${pageKey}.description`)); }; export function initializeI18n() { diff --git a/privacy/index.html b/privacy/index.html index 537063b..8eb87e6 100644 --- a/privacy/index.html +++ b/privacy/index.html @@ -3,6 +3,10 @@ + + + + @@ -12,6 +16,15 @@ + + + + + + + + + Privacy — Secure Tools diff --git a/tests/pdf-merge-and-categories.test.mjs b/tests/pdf-merge-and-categories.test.mjs index d5cc264..576a8ae 100644 --- a/tests/pdf-merge-and-categories.test.mjs +++ b/tests/pdf-merge-and-categories.test.mjs @@ -160,7 +160,9 @@ function testRoutesTranslationsAndPrivacy() { if (/^(?:https?:|mailto:|#)/i.test(rawReference)) continue; const reference = rawReference.split(/[?#]/)[0]; if (!reference) continue; - let target = path.resolve(path.dirname(file), reference); + let target = reference.startsWith("/") + ? path.resolve(root, reference.slice(1)) + : path.resolve(path.dirname(file), reference); if (reference.endsWith("/") || (fs.existsSync(target) && fs.statSync(target).isDirectory())) target = path.join(target, "index.html"); assert.equal(fs.existsSync(target), true, `${relative} has missing ${attribute}: ${rawReference}`); } diff --git a/tests/seo-foundation.test.mjs b/tests/seo-foundation.test.mjs index cec2b84..ae107fe 100644 --- a/tests/seo-foundation.test.mjs +++ b/tests/seo-foundation.test.mjs @@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const read = (relative) => fs.readFileSync(path.join(root, relative), "utf8"); +const readBytes = (relative) => fs.readFileSync(path.join(root, relative)); const origin = "https://securetools.app"; const indexableRoutes = new Map([ @@ -29,6 +30,15 @@ const indexableRoutes = new Map([ ]); const excludedRoutes = ["404.html", "tools/image-to-pdf/index.html"]; +const allHtmlRoutes = [...indexableRoutes.keys(), ...excludedRoutes]; +const shareImagePath = "assets/images/og-image.png"; +const shareImageUrl = `${origin}/${shareImagePath}`; +const iconLinks = new Map([ + ["assets/icons/favicon.ico", ''], + ["assets/icons/favicon-32x32.png", ''], + ["assets/icons/favicon-16x16.png", ''], + ["assets/icons/apple-touch-icon.png", ''], +]); const expectedUrls = [...indexableRoutes.values()].map((route) => `${origin}${route}`); const titles = new Set(); const descriptions = new Set(); @@ -54,6 +64,17 @@ for (const [relativeFile, route] of indexableRoutes) { assert.deepEqual(values(html, //g), [expectedUrl], `${relativeFile}: Open Graph URL matches canonical`); assert.equal(values(html, //g).length, 1, `${relativeFile}: one Open Graph title`); assert.equal(values(html, //g).length, 1, `${relativeFile}: one Open Graph description`); + const openGraphTitle = values(html, //g); + const openGraphDescription = values(html, //g); + assert.deepEqual(values(html, //g), [shareImageUrl], `${relativeFile}: same-origin Open Graph image`); + assert.deepEqual(values(html, //g), ["1200"], `${relativeFile}: Open Graph image width`); + assert.deepEqual(values(html, //g), ["630"], `${relativeFile}: Open Graph image height`); + assert.equal(values(html, //g).length, 1, `${relativeFile}: Open Graph image alternative`); + assert.deepEqual(values(html, //g), ["summary_large_image"], `${relativeFile}: Twitter card type`); + assert.deepEqual(values(html, //g), openGraphTitle, `${relativeFile}: Twitter title matches Open Graph`); + assert.deepEqual(values(html, //g), openGraphDescription, `${relativeFile}: Twitter description matches Open Graph`); + assert.deepEqual(values(html, //g), [shareImageUrl], `${relativeFile}: same-origin Twitter image`); + assert.equal(values(html, //g).length, 1, `${relativeFile}: Twitter image alternative`); assert.doesNotMatch(html, / { + const offset = 6 + (index * 16); + const width = ico[offset] || 256; + const height = ico[offset + 1] || 256; + return `${width}x${height}`; +}).sort(); +assert.deepEqual(icoSizes, ["16x16", "32x32", "48x48"]); + +const i18nRuntime = read("js/i18n.js"); +assert.match(i18nRuntime, /meta\[name="twitter:title"\]/, "i18n updates Twitter title"); +assert.match(i18nRuntime, /meta\[name="twitter:description"\]/, "i18n updates Twitter description"); + const robots = read("robots.txt"); assert.equal(robots, `User-agent: *\nAllow: /\n\nSitemap: ${origin}/sitemap.xml\n`); assert.doesNotMatch(robots, /^Disallow:\s*\/$/im, "robots.txt does not block the site"); @@ -91,4 +148,4 @@ assert.doesNotMatch(productionHtml, /google-analytics|googletagmanager|gtag\(|me assert.doesNotMatch(productionHtml, /]+src="https?:\/\//i, "no remote runtime scripts"); assert.doesNotMatch(productionHtml, /]+href="https?:\/\/[^\"]+"[^>]+rel="stylesheet"/i, "no remote stylesheets or fonts"); -console.log("SEO route, crawler, canonical, metadata, privacy, and custom-domain checks passed."); +console.log("SEO route, branding asset, canonical, metadata, privacy, and custom-domain checks passed."); diff --git a/tools/image-to-pdf/index.html b/tools/image-to-pdf/index.html index c1d823d..eeb7de7 100644 --- a/tools/image-to-pdf/index.html +++ b/tools/image-to-pdf/index.html @@ -3,6 +3,11 @@ + + + + + diff --git a/tools/image/compress/index.html b/tools/image/compress/index.html index 7e2825d..f737c23 100644 --- a/tools/image/compress/index.html +++ b/tools/image/compress/index.html @@ -1,10 +1,25 @@ + + + + + - Image Compressor — Secure Tools + + + + + + + + + + + Image Compressor — Secure Tools diff --git a/tools/image/converter/index.html b/tools/image/converter/index.html index 6c24ddb..6ede302 100644 --- a/tools/image/converter/index.html +++ b/tools/image/converter/index.html @@ -3,6 +3,11 @@ + + + + + @@ -12,6 +17,16 @@ + + + + + + + + + + Image Converter — Secure Tools diff --git a/tools/image/index.html b/tools/image/index.html index a39b35d..f7b9b49 100644 --- a/tools/image/index.html +++ b/tools/image/index.html @@ -3,6 +3,11 @@ + + + + + @@ -12,6 +17,16 @@ + + + + + + + + + + Image tools — Secure Tools diff --git a/tools/image/metadata/index.html b/tools/image/metadata/index.html index 916bb9c..948f36f 100644 --- a/tools/image/metadata/index.html +++ b/tools/image/metadata/index.html @@ -1,10 +1,25 @@ + + + + + - Image Metadata Inspector & Cleaner — Secure Tools + + + + + + + + + + + Image Metadata Inspector & Cleaner — Secure Tools diff --git a/tools/image/resize/index.html b/tools/image/resize/index.html index 96bd4ff..fbb7724 100644 --- a/tools/image/resize/index.html +++ b/tools/image/resize/index.html @@ -2,11 +2,26 @@ + + + + + - Image Resize — Secure Tools + + + + + + + + + + + Image Resize — Secure Tools diff --git a/tools/media/index.html b/tools/media/index.html index 91a2f24..1363d66 100644 --- a/tools/media/index.html +++ b/tools/media/index.html @@ -1,9 +1,24 @@ + + + + + - Media tools — Secure Tools + + + + + + + + + + + Media tools — Secure Tools

Tool category

Media tools

Small audio and video utilities designed for local processing.

  • Coming soon

    Audio converter

    Convert common audio formats on your device.

  • Coming soon

    Video trim

    Trim a clip without sending it to a server.

This category is planned. No unavailable item is presented as working.

diff --git a/tools/pdf/images-to-pdf/index.html b/tools/pdf/images-to-pdf/index.html index 109fc7a..29550a9 100644 --- a/tools/pdf/images-to-pdf/index.html +++ b/tools/pdf/images-to-pdf/index.html @@ -3,6 +3,11 @@ + + + + + @@ -12,6 +17,16 @@ + + + + + + + + + + Images to PDF — Secure Tools diff --git a/tools/pdf/index.html b/tools/pdf/index.html index dc19c59..c2c7b38 100644 --- a/tools/pdf/index.html +++ b/tools/pdf/index.html @@ -1,9 +1,24 @@ + + + + + - PDF tools — Secure Tools + + + + + + + + + + + PDF tools — Secure Tools diff --git a/tools/pdf/merge/index.html b/tools/pdf/merge/index.html index 929af7e..7b1eeab 100644 --- a/tools/pdf/merge/index.html +++ b/tools/pdf/merge/index.html @@ -1,9 +1,24 @@ + + + + + - Merge PDF — Secure Tools + + + + + + + + + + + Merge PDF — Secure Tools diff --git a/tools/pdf/metadata/index.html b/tools/pdf/metadata/index.html index 5fda596..ea50a2e 100644 --- a/tools/pdf/metadata/index.html +++ b/tools/pdf/metadata/index.html @@ -1,10 +1,25 @@ + + + + + - PDF Metadata Inspector & Cleaner — Secure Tools + + + + + + + + + + + PDF Metadata Inspector & Cleaner — Secure Tools diff --git a/tools/pdf/organize/index.html b/tools/pdf/organize/index.html index 19a6d18..ad934e8 100644 --- a/tools/pdf/organize/index.html +++ b/tools/pdf/organize/index.html @@ -1,10 +1,25 @@ + + + + + - Organize PDF — Secure Tools + + + + + + + + + + + Organize PDF — Secure Tools diff --git a/tools/pdf/split/index.html b/tools/pdf/split/index.html index 7ee1c1a..f4e4fef 100644 --- a/tools/pdf/split/index.html +++ b/tools/pdf/split/index.html @@ -1,9 +1,24 @@ + + + + + - Split PDF — Secure Tools + + + + + + + + + + + Split PDF — Secure Tools diff --git a/tools/pdf/to-images/index.html b/tools/pdf/to-images/index.html index 7cf5bd8..002df8f 100644 --- a/tools/pdf/to-images/index.html +++ b/tools/pdf/to-images/index.html @@ -1,9 +1,24 @@ + + + + + - PDF to Images — Secure Tools + + + + + + + + + + + PDF to Images — Secure Tools diff --git a/tools/privacy/index.html b/tools/privacy/index.html index 3f8d148..78d715c 100644 --- a/tools/privacy/index.html +++ b/tools/privacy/index.html @@ -1,9 +1,24 @@ + + + + + - Privacy metadata tools — Secure Tools + + + + + + + + + + + Privacy metadata tools — Secure Tools
diff --git a/tools/scan/index.html b/tools/scan/index.html index 888b9c7..108b102 100644 --- a/tools/scan/index.html +++ b/tools/scan/index.html @@ -1,9 +1,24 @@ + + + + + - Scan and OCR tools — Secure Tools + + + + + + + + + + + Scan and OCR tools — Secure Tools

Tool category

Scan & OCR tools

Turn scans into useful documents while keeping source material on your device.

  • Coming soon

    Image to text

    Recognize text from an image locally.

  • Coming soon

    Document scanner

    Crop and clean photographed documents.

This category is planned. No unavailable item is presented as working.