Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<a class="skip-link" href="#main" data-i18n="common.skip">Skip to main content</a>
<header class="site-header"><div class="container header-inner"><a class="brand" href="./" aria-label="Secure Tools home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><div class="header-controls"><label class="control-label"><span data-i18n="common.language">Language</span><select class="control" data-language-select><option value="en">English</option><option value="ko">한국어</option><option value="ja">日本語</option><option value="es">Español</option><option value="de">Deutsch</option><option value="fr">Français</option></select></label><label class="control-label"><span data-i18n="common.theme">Theme</span><select class="control" data-theme-select><option value="system" data-i18n="common.themes.system">System</option><option value="light" data-i18n="common.themes.light">Light</option><option value="dark" data-i18n="common.themes.dark">Dark</option></select></label></div></div></header>
<main id="main" class="page-main not-found"><div class="container prose"><p class="eyebrow" data-i18n="notFound.eyebrow">404</p><h1 data-i18n="notFound.title">This page is not here.</h1><p data-i18n="notFound.description">The address may be outdated or mistyped.</p><a class="button button--primary" href="./" data-i18n="notFound.action">Return home</a></div></main>
<footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="./"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation"><a href="./#tools" data-i18n="common.nav.tools">Tools</a><a href="./privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="./about/" data-i18n="common.nav.about">About</a><a href="https://github.com/maruson08/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer>
<footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="./" aria-label="Secure Tools home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation"><a href="./#tools" data-i18n="common.nav.tools">Tools</a><a href="./privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="./about/" data-i18n="common.nav.about">About</a><a href="https://github.com/maruson08/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer>
<script type="module" src="./js/main.js"></script>
</body>
</html>
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ The hub is a static site built with semantic HTML, CSS, and Vanilla JavaScript E
│ ├── shared/
│ │ ├── file.js
│ │ ├── pdf.js
│ │ ├── tool.css
│ │ └── save.js
│ ├── pdf/
│ │ ├── index.html
Expand Down Expand Up @@ -104,6 +105,8 @@ The hub is a static site built with semantic HTML, CSS, and Vanilla JavaScript E

`js/main.js` initializes shared theme and internationalization behavior. `js/theme.js` owns Light, Dark, and System selection, OS color-scheme observation, and persistence. `js/i18n.js` resolves all six supported languages from browser preferences, applies `data-i18n` bindings without reloading, updates metadata and `<html lang>`, and gives persisted manual selection precedence. Repository links are centralized in `js/config.js`.

The concise [UX consistency audit](./docs/ux-consistency-audit.md) records the shared interaction patterns, resolved drift, and intentional tool-specific exceptions established before v1.0.0 hardening.

The category hubs are:

- `/tools/pdf/`
Expand Down
2 changes: 1 addition & 1 deletion about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h2 data-i18n="about.philosophyTitle">Local-first and open</h2>
<p data-i18n="about.philosophyBody">The project favors static, understandable code and a privacy model that anyone can verify through source and network inspection.</p>
</article>
</main>
<footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="../"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation"><a href="../#tools" data-i18n="common.nav.tools">Tools</a><a href="../privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="./" data-i18n="common.nav.about">About</a><a href="https://github.com/maruson08/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer>
<footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="../" aria-label="Secure Tools home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation"><a href="../#tools" data-i18n="common.nav.tools">Tools</a><a href="../privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="./" data-i18n="common.nav.about">About</a><a href="https://github.com/maruson08/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer>
<script type="module" src="../js/main.js"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
--focus: #1673c9;
--status-muted: #74766f;
--shadow-sm: 0 1px 2px rgb(23 24 22 / 5%);
--preview-backdrop: #d7d8da;
--radius-sm: 0.4rem;
--radius-md: 0.75rem;
--radius-lg: 1.15rem;
Expand Down Expand Up @@ -48,6 +49,7 @@
--focus: #66b5ff;
--status-muted: #9b9e96;
--shadow-sm: 0 1px 2px rgb(0 0 0 / 20%);
--preview-backdrop: #2b2e29;
}

*, *::before, *::after { box-sizing: border-box; }
Expand Down
1 change: 1 addition & 0 deletions css/categories.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.category-tool { display: flex; min-width: 0; min-height: 12rem; flex-direction: column; align-items: flex-start; padding: var(--space-5); color: inherit; text-decoration: none; }
.category-tool__top { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: var(--space-4); margin-bottom: auto; }
.category-tool h2 { margin: var(--space-5) 0 var(--space-2); font-size: clamp(1.3rem, 3vw, 1.75rem); }
.category-tool > .status { margin-bottom: auto; }
.category-tool p { margin: 0; color: var(--text-secondary); }
a.category-tool { transition: border-color var(--transition-fast), transform var(--transition-fast); }
a.category-tool:hover { border-color: var(--border-strong); transform: translateY(-2px); }
Expand Down
9 changes: 6 additions & 3 deletions css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
max-width: 100%;
text-align: center;
text-decoration: none;
cursor: pointer;
transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.button--primary { background: var(--text-primary); color: var(--bg-primary); }
.button--primary:hover { background: var(--accent-strong); color: var(--text-inverse); }
.button--primary:hover:not(:disabled) { background: var(--accent-strong); color: var(--text-inverse); }
.button--secondary { border-color: var(--border); background: var(--bg-secondary); }
.button--secondary:hover { border-color: var(--border-strong); background: var(--bg-subtle); }
.button--secondary:hover:not(:disabled) { border-color: var(--border-strong); background: var(--bg-subtle); }
.button:disabled { border-color: var(--border); background: var(--bg-subtle); color: var(--status-muted); cursor: not-allowed; }

.control {
min-height: 2.5rem;
Expand All @@ -32,7 +34,8 @@

.control[data-language-select] { width: 9rem; max-width: 9rem; }

.control:hover { border-color: var(--border-strong); }
.control:hover:not(:disabled) { border-color: var(--border-strong); }
.control:disabled { border-color: var(--border); background: var(--bg-subtle); color: var(--status-muted); cursor: not-allowed; }
.control-label { display: inline-flex; min-width: 0; align-items: center; gap: var(--space-2); color: var(--text-secondary); font-size: 0.75rem; font-weight: 650; }
.surface { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-secondary); box-shadow: var(--shadow-sm); }
.status { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--status-muted); font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap; }
Expand Down
53 changes: 53 additions & 0 deletions docs/ux-consistency-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# UX consistency audit

Sprint 10 audited the homepage, About, Privacy, all five category hubs, the PDF hub, and the six production file tools at source level. The review covered file input, source and empty states, queues, settings, actions, progress, errors, results, save behavior, privacy messaging, shared chrome, responsive behavior, themes, and accessibility.

## Inventory and resolutions

| Pattern | Pages/tools | Finding | Resolution |
| --- | --- | --- | --- |
| Shared tool foundation | All six file tools | Common styles were owned by Images to PDF; PDF to Images also imported Split CSS. | Moved file input, source, queue, mode, form, action, progress, and status rules to `tools/shared/tool.css`; removed sibling stylesheet dependencies. |
| File input | All six file tools | The interaction was already a full-area native label with keyboard focus and drag/drop, but the image accept filter existed only at runtime. | Preserved the shared semantic pattern and added the supported image formats to static markup. Busy inputs now expose the same disabled visual treatment as their actual state. |
| Privacy note | All six file tools | Placement and localization were already consistent. | Preserved exactly one local-processing note beside each drop zone. |
| Empty state | Images to PDF, Merge PDF | Queue copy repeated ordering instructions already present near the input. | Shortened empty copy in all six languages. `[hidden]` remains explicitly protected in shared CSS. |
| Source summary | Split, PDF to Images, Organizer, Metadata | Split and PDF to Images shared a full source card; Organizer and Metadata use toolbar summaries suited to larger workspaces. | Consolidated the full source card and added an explicit compact source-state variant for the two complex workspaces. |
| Queue/list | Images to PDF, Merge PDF | Both used the same visual grammar under an image-specific list class name. | Renamed the shared presentation class to `file-list`; item content remains tool-specific. |
| Settings | Images to PDF, Merge, Split, PDF to Images | Labels, controls, help, checks, and sidebars were already structurally shared, but some selectors were duplicated. | Consolidated field/mode rules and retained each tool's necessary controls. |
| Primary actions | All six file tools | Shared sizing existed, but disabled buttons still reacted to hover. | Standardized pointer, hover, disabled color, border, background, and cursor behavior. |
| Secondary/destructive actions | Queues, Organizer, Metadata | Remove actions were restrained and context-specific; disabled icon/page actions relied mainly on opacity. | Preserved restrained destructive hover/color treatment and gave disabled actions explicit theme-aware colors. |
| Loading/progress | All six file tools | Progress and live status were colocated with actions, but status feedback appeared as unstructured text. | Kept existing progress semantics and converted non-empty status messages to one bordered, theme-aware neutral/error/warning/success pattern. |
| Error state | All six file tools | Errors used localized text and live regions without alerts or unsafe HTML, but visual treatment varied by tone. | Standardized error, warning, and success tokens and panels; field-level errors retain `role="alert"`. |
| Result/save | Organizer, Metadata, other production tools | Organizer and Metadata duplicated the same output grid. Other tools intentionally save immediately through the shared picker/download fallback. | Added a shared responsive `tool-output` pattern for Organizer and Metadata; retained immediate saving elsewhere. |
| Header | All production pages | Header structure was consistent; some category-page brand links lacked the established accessible name. | Applied the same home-link name throughout; preserved the shared navigation and language/theme controls. |
| Footer | Tool and category pages | Link sets differed, and planned category hubs omitted footer navigation. | Standardized order to Tools, Privacy, About, Source on all production pages. |
| Category hubs | PDF, Image, Privacy, Scan/OCR, Media | Available and planned semantics were correct, but planned-card title alignment differed. | Kept available tools as links and planned tools as non-interactive articles; aligned their status/title rhythm. |
| Light/Dark | Organizer and tool states | The thumbnail surround used one light hardcoded backdrop; warning/success state ownership was inconsistent. | Added theme tokens for the non-document preview surround and centralized tool feedback colors. PDF canvas white remains intentionally unchanged. |
| Responsive/i18n | All production pages and six locales | Sprint 9 foundations were sound; shared output/source rules had drifted across sibling stylesheets. | Preserved language-specific line breaking, shrinkable flex/grid children, wrapping buttons, stacked outputs, mobile metadata rows, and header control wrapping. |
| Reduced motion | Shared and tool-specific transitions | Existing protection was complete. | Preserved the global reduced-motion override and tool-specific transition removals. |

## Accessibility checklist

- [x] Skip links and main targets are present on every production page.
- [x] Full-area file labels activate native inputs by pointer and keyboard.
- [x] File inputs have descriptions and accepted-type declarations.
- [x] Drag-active, focus-visible, disabled, and busy/loading affordances remain explicit.
- [x] Every production tool exposes a polite live status region.
- [x] Real progress elements retain localized accessible names.
- [x] Queue and page icon controls retain localized names and keyboard equivalents.
- [x] Form labels, help references, field alerts, radio legends, and metadata checkboxes remain associated.
- [x] No browser `alert()` or unsafe translated HTML sink was introduced.
- [x] Header/footer landmarks and brand links are consistent.

## Intentional exceptions

- Organizer keeps a page grid because queue rows cannot represent page previews, rotation, and page-level actions clearly.
- Metadata keeps a responsive semantic table on wider screens and structured rows on narrow screens.
- Split and PDF to Images use full source cards; Organizer and Metadata use compact summaries inside their existing workspaces.
- The PDF preview canvas stays white because it represents document content, while only its surrounding backdrop follows the theme.
- PDF to Images allows safe source replacement by cancelling the active conversion first; other tools disable source replacement while busy.
- Tools continue to save through the established picker/download fallback. A separate “Ready to save” panel would add a second action without improving the existing flow.
- Category navigation may scroll horizontally on very narrow screens by design; the page body itself must not overflow.

## Validation boundary

Automated checks cover architecture, semantics, resources, six-language keys, known overflow anti-patterns, CSP, and functional regressions. Browser automation remained subject to the known Windows sandbox ACL limitation during this Sprint. If visual control is unavailable, real human browser review at 360, 390, 768, 1024, and 1440 CSS pixels in Light/Dark themes remains required before v1.0.0 release approval.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ <h2 data-i18n="openSource.title">Open by design.</h2>

<footer class="site-footer">
<div class="container footer-inner">
<div><a class="brand" href="./"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div>
<div><a class="brand" href="./" aria-label="Secure Tools home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div>
<nav aria-label="Footer navigation">
<a href="#tools" data-i18n="common.nav.tools">Tools</a>
<a href="./privacy/" data-i18n="common.nav.privacy">Privacy</a>
Expand Down
4 changes: 2 additions & 2 deletions js/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export const de = {
"queue": {
"title": "PDF-Bestellung",
"count": "PDFs: {count}",
"empty": "Noch keine PDFs ausgewählt. Hinzugefügte Dateien werden von oben nach unten zusammengeführt.",
"empty": "Noch keine PDFs hinzugefügt.",
"meta": "{size} · {pages} Seite(n)",
"moveUp": "PDF nach oben verschieben",
"moveDown": "PDF nach unten verschieben",
Expand Down Expand Up @@ -401,7 +401,7 @@ export const de = {
"queue": {
"title": "Bildreihenfolge",
"count": "Bilder: {count}",
"empty": "Noch keine Bilder ausgewählt. Hinzugefügte Bilder werden hier in der Reihenfolge der PDF-Seiten angezeigt.",
"empty": "Noch keine Bilder hinzugefügt.",
"previewAlt": "Vorschau von {name}",
"meta": "{size} · Seite {page}",
"moveUp": "Bild nach oben verschieben",
Expand Down
4 changes: 2 additions & 2 deletions js/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const en = {
eyebrow: "PDF tool", title: "Merge PDF", description: "Arrange PDF files and combine every page into one document. Files stay on this device.",
drop: { title: "Add your PDFs", description: "Drop PDF files here or use the picker. The queue order becomes the document order.", choose: "Choose PDFs", localTitle: "Processed locally.", localBody: "Your PDFs never leave this device.", privacyLink: "How privacy works" },
queue: {
title: "PDF order", count: "PDFs: {count}", empty: "No PDFs selected yet. Added files will be merged from top to bottom.",
title: "PDF order", count: "PDFs: {count}", empty: "No PDFs added yet.",
meta: "{size} · {pages} page(s)", moveUp: "Move PDF up", moveDown: "Move PDF down", remove: "Remove PDF",
},
settings: {
Expand All @@ -159,7 +159,7 @@ export const en = {
eyebrow: "Image tool", title: "Images to PDF",
description: "Arrange JPEG, PNG, and WebP images and save them as one PDF. Everything happens on this device.",
drop: { title: "Add your images", description: "Drop files here or use the picker. JPEG, PNG, and WebP can be mixed.", choose: "Choose images", localTitle: "Processed locally.", localBody: "Your images never leave this device.", privacyLink: "How privacy works" },
queue: { title: "Image order", count: "Images: {count}", empty: "No images selected yet. Added images will appear here in PDF page order.", previewAlt: "Preview of {name}", meta: "{size} · page {page}", moveUp: "Move image up", moveDown: "Move image down", remove: "Remove image" },
queue: { title: "Image order", count: "Images: {count}", empty: "No images added yet.", previewAlt: "Preview of {name}", meta: "{size} · page {page}", moveUp: "Move image up", moveDown: "Move image down", remove: "Remove image" },
settings: {
title: "PDF settings", filename: "Output filename", filenameHelp: "A .pdf extension is added automatically.", pageSize: "Page size", a4: "A4", letter: "Letter", imageSize: "Match image",
orientation: "Orientation", auto: "Automatic", portrait: "Portrait", landscape: "Landscape", fit: "Image fit", contain: "Fit entire image", cover: "Fill page",
Expand Down
Loading
Loading