8 Commits

19 changed files with 363 additions and 274 deletions

View File

@@ -4,6 +4,28 @@ All notable changes to `pdf-tools` are documented here.
The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements. The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements.
## 0.4.1 — Stable Toolbox header layout
### Changed
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.1`.
- Adopted the stable shared top bar: the fixed left cluster contains Personalize, Apps, the PDF-specific Gitea source link, and Help; the app title and one tagline remain centered; and the fixed far-right block contains the Toolbox icon and `add·ideas Toolbox` label.
## 0.4.0 — Shared Toolbox header and themes
### Added
- Added the standard Toolbox header controls for in-app help, the app-specific Gitea source link, appearance personalization, and application switching.
- Added explicit light, dark, and system-following themes for the PDF workspace, saved-workspace list, merge queue, dialogs, status messages, and controls.
### Changed
- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.0`.
- The header now uses the manifest's repository metadata for the source link instead of carrying a release-specific source action.
- Replaced application color literals with semantic PDF workspace variables while intentionally keeping rendered PDF pages on a white paper surface.
- Scoped application button and card styles to the shell's main content so they cannot override shared header controls.
- Bumped the app/package version to `0.4.0`.
## 0.3.4 — PDF.js compatibility and Toolbox integration ## 0.3.4 — PDF.js compatibility and Toolbox integration
### Added ### Added

View File

@@ -4,7 +4,7 @@
Current hosted version: <https://pdftools.add-ideas.de> Current hosted version: <https://pdftools.add-ideas.de>
Current release branch: **v0.3.4 — PDF.js `toHex` browser compatibility fix**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history. Current development release: **v0.4.1 — stable Toolbox header layout**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history.
Active work is tracked in Gitea issues. Durable project context is mirrored into the Gitea wiki from repository docs, including [`ROADMAP.md`](ROADMAP.md). Active work is tracked in Gitea issues. Durable project context is mirrored into the Gitea wiki from repository docs, including [`ROADMAP.md`](ROADMAP.md).
@@ -43,6 +43,7 @@ This makes the project especially useful for self-hosted environments, public-se
- **Stable page references**: Duplicated pages and reordered pages are tracked as workspace page references rather than only by original page number. - **Stable page references**: Duplicated pages and reordered pages are tracked as workspace page references rather than only by original page number.
- **Merge choices**: Loading another PDF can replace the current document, append pages, or insert pages at a chosen position. - **Merge choices**: Loading another PDF can replace the current document, append pages, or insert pages at a chosen position.
- **PDF.js compatibility setup**: PDF.js is configured through one shared module and a local worker wrapper so the app can polyfill `Uint8Array.prototype.toHex` before PDF.js runs in older browsers. - **PDF.js compatibility setup**: PDF.js is configured through one shared module and a local worker wrapper so the app can polyfill `Uint8Array.prototype.toHex` before PDF.js runs in older browsers.
- **Toolbox appearance controls**: The shared header provides light, dark, and system-following themes and keeps the app switcher, help, and source controls in the same place as the Toolbox portal.
- **In-app help**: The app includes a Help/Tutorial dialog with keyboard shortcuts and workflow explanations. - **In-app help**: The app includes a Help/Tutorial dialog with keyboard shortcuts and workflow explanations.
## Current features ## Current features
@@ -96,7 +97,7 @@ Keyboard shortcuts are ignored while typing in form fields.
## In-app documentation concept ## In-app documentation concept
The app includes a Help/Tutorial dialog reachable from the header via **Help ?**, `F1`, or `?`. The app includes a Help/Tutorial dialog reachable from the header's **?** control, `F1`, or `?`.
Recommended structure for in-app documentation: Recommended structure for in-app documentation:
@@ -126,10 +127,11 @@ The production build is relocatable and can also be assembled into an
[Toolbox App Contract](https://git.add-ideas.de/zemion/toolbox-sdk). The same [Toolbox App Contract](https://git.add-ideas.de/zemion/toolbox-sdk). The same
artifact remains fully usable on its own. artifact remains fully usable on its own.
When launched with a same-origin catalogue URL, for example The shared first-party shell always provides help, source, and appearance
`?toolbox=/toolbox.catalog.json`, the shared first-party shell adds a toolbox controls. When launched with a same-origin catalogue URL, for example
home link and application switcher. Missing, invalid, and cross-origin `?toolbox=/toolbox.catalog.json`, it also connects the application switcher to
catalogues are ignored so the app falls back to standalone mode. the current Toolbox catalogue. Missing, invalid, and cross-origin catalogues
are ignored so the app falls back to standalone mode.
### Build and test from source ### Build and test from source
@@ -255,10 +257,10 @@ The application version shown in the header is defined in `src/version.ts`. The
The current development baseline is: The current development baseline is:
```text ```text
v0.3.4 — PDF.js toHex browser compatibility fix v0.4.1 — stable Toolbox header and light/dark/system themes
``` ```
This branch preserves the browser-only workspace baseline, includes the multi-file merge queue from the `0.3.2` milestone, bumps the displayed app/package version to `0.3.4`, and adds a shared PDF.js setup with a `toHex` compatibility shim for older browsers. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting are separated enough to support additional feature work without turning `App.tsx` back into a monolith. This branch preserves the browser-only workspace baseline and PDF.js compatibility setup while moving the app to Toolbox SDK `0.2.1`. The displayed app/package version is `0.4.1`; the shared header keeps Personalize, Apps, the PDF source link, and Help in a fixed left cluster, the app title and one tagline centered, and the Toolbox icon with the `add·ideas Toolbox` label in a fixed far-right block. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting remain separated enough to support additional feature work without turning `App.tsx` back into a monolith.
## Project structure ## Project structure

32
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{ {
"name": "pdf-tools", "name": "pdf-tools",
"version": "0.3.4", "version": "0.4.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pdf-tools", "name": "pdf-tools",
"version": "0.3.4", "version": "0.4.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "^0.1.0", "@add-ideas/toolbox-contract": "^0.2.1",
"@add-ideas/toolbox-shell-react": "^0.1.0", "@add-ideas/toolbox-shell-react": "^0.2.1",
"fflate": "^0.8.3", "fflate": "^0.8.3",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.7.284", "pdfjs-dist": "^5.7.284",
@@ -18,7 +18,7 @@
"react-dom": "^19.2.6" "react-dom": "^19.2.6"
}, },
"devDependencies": { "devDependencies": {
"@add-ideas/toolbox-testkit": "^0.1.0", "@add-ideas/toolbox-testkit": "^0.2.1",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
@@ -40,18 +40,18 @@
} }
}, },
"node_modules/@add-ideas/toolbox-contract": { "node_modules/@add-ideas/toolbox-contract": {
"version": "0.1.1", "version": "0.2.1",
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.1.1/toolbox-contract-0.1.1.tgz", "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.2.1/toolbox-contract-0.2.1.tgz",
"integrity": "sha512-0q2whA5Y4SqG4PNNleXYR8nGbT3UYsZ1x+6ZWVmxuc/WjMxmujhD7ilIISmFzmnNdbyEyv/Zpr+U0fnHqGODqw==", "integrity": "sha512-NmwV41+ZS0qBWUnI9vflm0PcsiqITmxpSQQ5vMVaToFZuSSIJupgM43tuV9G4OJ1En0eTdYNxbsd3nsYhju2kw==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@add-ideas/toolbox-shell-react": { "node_modules/@add-ideas/toolbox-shell-react": {
"version": "0.1.1", "version": "0.2.1",
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.1.1/toolbox-shell-react-0.1.1.tgz", "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.2.1/toolbox-shell-react-0.2.1.tgz",
"integrity": "sha512-Di2mu1BoJrWJg4/v5jDExROXDpvLXorlUgqcsBQV8ea/LklKoBFTAfgTkk/23HZ2BzixyMIjrhl9kD507s+iPQ==", "integrity": "sha512-slpz9ODVbk27b+uvG3WeCmNa71Q6guabyVHT+jyGLT6ENLSbwmoEFZ0mKNJDxemltpt9KY8I7z47uNBsQ7yKYg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "0.1.1" "@add-ideas/toolbox-contract": "0.2.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=18 <20", "react": ">=18 <20",
@@ -59,13 +59,13 @@
} }
}, },
"node_modules/@add-ideas/toolbox-testkit": { "node_modules/@add-ideas/toolbox-testkit": {
"version": "0.1.1", "version": "0.2.1",
"resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.1.1/toolbox-testkit-0.1.1.tgz", "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.2.1/toolbox-testkit-0.2.1.tgz",
"integrity": "sha512-o+H7LfM7ipdgmYz18S0lLuN6JsXUR+8ubAbcjTWTk8rSqNfp1JyeoRHOPbJVEHXrQwIFe/OB86UzRpex2gnKUQ==", "integrity": "sha512-0Nfs92r2M5I6hvP8TM776XG08StCBNS40sWt+t4wiMroRMS0aWk/hYqU6+I/VK1lzTLbtxSi0k33trJat3io6Q==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "0.1.1" "@add-ideas/toolbox-contract": "0.2.1"
}, },
"bin": { "bin": {
"toolbox-check": "dist/cli.js" "toolbox-check": "dist/cli.js"

View File

@@ -1,6 +1,6 @@
{ {
"name": "pdf-tools", "name": "pdf-tools",
"version": "0.3.4", "version": "0.4.1",
"private": true, "private": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
@@ -23,8 +23,8 @@
"check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox" "check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox"
}, },
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "^0.1.0", "@add-ideas/toolbox-contract": "^0.2.1",
"@add-ideas/toolbox-shell-react": "^0.1.0", "@add-ideas/toolbox-shell-react": "^0.2.1",
"fflate": "^0.8.3", "fflate": "^0.8.3",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.7.284", "pdfjs-dist": "^5.7.284",
@@ -32,7 +32,7 @@
"react-dom": "^19.2.6" "react-dom": "^19.2.6"
}, },
"devDependencies": { "devDependencies": {
"@add-ideas/toolbox-testkit": "^0.1.0", "@add-ideas/toolbox-testkit": "^0.2.1",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",

View File

@@ -8,13 +8,6 @@
"icon": "./favicon.svg", "icon": "./favicon.svg",
"categories": ["documents", "pdf"], "categories": ["documents", "pdf"],
"tags": ["merge", "split", "rotate", "reorder"], "tags": ["merge", "split", "rotate", "reorder"],
"actions": [
{
"id": "source",
"label": "Source",
"url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4"
}
],
"integration": { "integration": {
"contextVersion": 1, "contextVersion": 1,
"launchModes": ["navigate", "new-tab"], "launchModes": ["navigate", "new-tab"],
@@ -35,5 +28,5 @@
"repository": "https://git.add-ideas.de/zemion/pdf-tools", "repository": "https://git.add-ideas.de/zemion/pdf-tools",
"license": "AGPL-3.0-only" "license": "AGPL-3.0-only"
}, },
"version": "0.3.4" "version": "0.4.1"
} }

View File

@@ -1143,17 +1143,10 @@ const App: React.FC = () => {
<AppShell <AppShell
app={toolboxApp} app={toolboxApp}
className="pdf-toolbox-shell" className="pdf-toolbox-shell"
appActions={ helpAction={{
<button onClick: () => setHelpOpen(true),
type="button" title: 'Open help and keyboard shortcuts (F1 or ?)',
className="app-help-button" }}
onClick={() => setHelpOpen(true)}
aria-haspopup="dialog"
title="Open help and keyboard shortcuts (F1 or ?)"
>
Help <span aria-hidden="true">?</span>
</button>
}
> >
<FileLoader pdf={pdf} onFilesLoaded={handleFilesLoaded} /> <FileLoader pdf={pdf} onFilesLoaded={handleFilesLoaded} />
@@ -1237,7 +1230,11 @@ const App: React.FC = () => {
{error && ( {error && (
<div <div
className="card" className="card"
style={{ border: '1px solid #fecaca', background: '#fef2f2' }} style={{
border: '1px solid var(--pdf-danger-border)',
background: 'var(--pdf-danger-surface)',
color: 'var(--pdf-danger-text)',
}}
> >
<strong>Error:</strong> {error} <strong>Error:</strong> {error}
</div> </div>

View File

@@ -21,18 +21,18 @@ const backgroundByVariant: Record<
NonNullable<ActionDialogAction['variant']>, NonNullable<ActionDialogAction['variant']>,
string string
> = { > = {
primary: '#2563eb', primary: 'var(--pdf-primary)',
secondary: '#e5e7eb', secondary: 'var(--pdf-control)',
danger: '#dc2626', danger: 'var(--pdf-danger-text)',
}; };
const colorByVariant: Record< const colorByVariant: Record<
NonNullable<ActionDialogAction['variant']>, NonNullable<ActionDialogAction['variant']>,
string string
> = { > = {
primary: 'white', primary: 'var(--pdf-on-primary)',
secondary: '#111827', secondary: 'var(--pdf-control-text)',
danger: 'white', danger: 'var(--pdf-on-primary)',
}; };
const ActionDialog: React.FC<ActionDialogProps> = ({ const ActionDialog: React.FC<ActionDialogProps> = ({
@@ -75,7 +75,7 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
position: 'fixed', position: 'fixed',
inset: 0, inset: 0,
zIndex: 70, zIndex: 70,
background: 'rgba(15, 23, 42, 0.55)', background: 'var(--pdf-overlay)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@@ -86,9 +86,10 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
style={{ style={{
width: '100%', width: '100%',
maxWidth: '440px', maxWidth: '440px',
background: 'white', background: 'var(--pdf-surface-raised)',
color: 'var(--pdf-text)',
borderRadius: '0.75rem', borderRadius: '0.75rem',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)', boxShadow: 'var(--pdf-dialog-shadow)',
padding: '1rem', padding: '1rem',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -121,8 +122,8 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
borderRadius: '999px', borderRadius: '999px',
width: '1.8rem', width: '1.8rem',
height: '1.8rem', height: '1.8rem',
background: '#e5e7eb', background: 'var(--pdf-control)',
color: '#111827', color: 'var(--pdf-control-text)',
cursor: 'pointer', cursor: 'pointer',
fontSize: '1.1rem', fontSize: '1.1rem',
lineHeight: 1, lineHeight: 1,
@@ -139,7 +140,7 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
<div <div
style={{ style={{
fontSize: '0.9rem', fontSize: '0.9rem',
color: '#4b5563', color: 'var(--pdf-muted)',
lineHeight: 1.45, lineHeight: 1.45,
}} }}
> >
@@ -173,9 +174,11 @@ const ActionDialog: React.FC<ActionDialogProps> = ({
borderRadius: '0.5rem', borderRadius: '0.5rem',
padding: '0.45rem 0.8rem', padding: '0.45rem 0.8rem',
background: action.disabled background: action.disabled
? '#e5e7eb' ? 'var(--pdf-control)'
: backgroundByVariant[variant], : backgroundByVariant[variant],
color: action.disabled ? '#6b7280' : colorByVariant[variant], color: action.disabled
? 'var(--pdf-muted-soft)'
: colorByVariant[variant],
cursor: action.disabled ? 'default' : 'pointer', cursor: action.disabled ? 'default' : 'pointer',
fontSize: '0.9rem', fontSize: '0.9rem',
}} }}

View File

@@ -49,7 +49,7 @@ const ActionsPanel: React.FC<ActionsPanelProps> = ({
return ( return (
<div className="card"> <div className="card">
<h2>Tools</h2> <h2>Tools</h2>
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
Use these tools on the current in-memory document (reordered, rotated, Use these tools on the current in-memory document (reordered, rotated,
with deletions). Nothing is uploaded to a server. with deletions). Nothing is uploaded to a server.
</p> </p>

View File

@@ -30,7 +30,13 @@ const FileLoader: React.FC<FileLoaderProps> = ({ pdf, onFilesLoaded }) => {
/> />
{pdf && ( {pdf && (
<div style={{ marginTop: '0.75rem', fontSize: '0.9rem' }}> <div
style={{
marginTop: '0.75rem',
fontSize: '0.9rem',
color: 'var(--pdf-muted)',
}}
>
<div> <div>
<strong>Loaded:</strong> {pdf.name} <strong>Loaded:</strong> {pdf.name}
</div> </div>

View File

@@ -54,22 +54,25 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
return ( return (
<div <div
className="card merge-queue-card" className="card merge-queue-card"
style={{ border: '1px solid #bfdbfe', background: '#eff6ff' }} style={{
border: '1px solid var(--pdf-info-border)',
background: 'var(--pdf-info-surface)',
}}
> >
<h2>Merge PDF queue</h2> <h2>Merge PDF queue</h2>
<p style={{ fontSize: '0.85rem', color: '#374151' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
Add several PDFs, reorder the queue, then merge them into a new unsaved Add several PDFs, reorder the queue, then merge them into a new unsaved
workspace. Processing still happens entirely in your browser. workspace. Processing still happens entirely in your browser.
</p> </p>
{hasCurrentPdf ? ( {hasCurrentPdf ? (
<p style={{ fontSize: '0.85rem', color: '#374151' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
Current workspace:{' '} Current workspace:{' '}
<strong>{currentPdfName ?? 'Untitled document'}</strong> with{' '} <strong>{currentPdfName ?? 'Untitled document'}</strong> with{' '}
{currentPageCount} {currentPageCount === 1 ? 'page' : 'pages'}. {currentPageCount} {currentPageCount === 1 ? 'page' : 'pages'}.
</p> </p>
) : ( ) : (
<p style={{ fontSize: '0.85rem', color: '#374151' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
No current workspace is open. The queue will become a new workspace. No current workspace is open. The queue will become a new workspace.
</p> </p>
)} )}
@@ -122,7 +125,7 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
))} ))}
</div> </div>
<p style={{ fontSize: '0.85rem', color: '#374151' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-info-text)' }}>
Queue total:{' '} Queue total:{' '}
<strong> <strong>
{items.length} {items.length === 1 ? 'PDF' : 'PDFs'} {items.length} {items.length === 1 ? 'PDF' : 'PDFs'}
@@ -186,7 +189,7 @@ const MergeQueuePanel: React.FC<MergeQueuePanelProps> = ({
fontSize: '0.85rem', fontSize: '0.85rem',
}} }}
/>{' '} />{' '}
<span style={{ color: '#6b7280' }}> <span style={{ color: 'var(--pdf-muted-soft)' }}>
(1 = before first page, {currentPageCount + 1} = after last page) (1 = before first page, {currentPageCount + 1} = after last page)
</span> </span>
</span> </span>

View File

@@ -48,7 +48,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
position: 'fixed', position: 'fixed',
inset: 0, inset: 0,
zIndex: 60, zIndex: 60,
background: 'rgba(15, 23, 42, 0.55)', background: 'var(--pdf-overlay)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@@ -60,9 +60,10 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
style={{ style={{
width: '100%', width: '100%',
maxWidth: '420px', maxWidth: '420px',
background: 'white', background: 'var(--pdf-surface-raised)',
color: 'var(--pdf-text)',
borderRadius: '0.75rem', borderRadius: '0.75rem',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)', boxShadow: 'var(--pdf-dialog-shadow)',
padding: '1rem', padding: '1rem',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -95,8 +96,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
borderRadius: '999px', borderRadius: '999px',
width: '1.8rem', width: '1.8rem',
height: '1.8rem', height: '1.8rem',
background: '#e5e7eb', background: 'var(--pdf-control)',
color: '#111827', color: 'var(--pdf-control-text)',
cursor: 'pointer', cursor: 'pointer',
fontSize: '1.1rem', fontSize: '1.1rem',
lineHeight: 1, lineHeight: 1,
@@ -114,7 +115,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
style={{ style={{
margin: 0, margin: 0,
fontSize: '0.9rem', fontSize: '0.9rem',
color: '#4b5563', color: 'var(--pdf-muted)',
}} }}
> >
Copy{' '} Copy{' '}
@@ -145,7 +146,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
style={{ style={{
padding: '0.45rem 0.55rem', padding: '0.45rem 0.55rem',
borderRadius: '0.5rem', borderRadius: '0.5rem',
border: '1px solid #d1d5db', border: '1px solid var(--pdf-border-strong)',
fontSize: '0.95rem', fontSize: '0.95rem',
}} }}
/> />
@@ -154,7 +155,7 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
<div <div
style={{ style={{
fontSize: '0.8rem', fontSize: '0.8rem',
color: '#6b7280', color: 'var(--pdf-muted-soft)',
lineHeight: 1.4, lineHeight: 1.4,
}} }}
> >
@@ -166,9 +167,9 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
<div <div
style={{ style={{
borderRadius: '0.5rem', borderRadius: '0.5rem',
background: '#fef2f2', background: 'var(--pdf-danger-surface)',
border: '1px solid #fecaca', border: '1px solid var(--pdf-danger-border)',
color: '#b91c1c', color: 'var(--pdf-danger-text)',
padding: '0.5rem', padding: '0.5rem',
fontSize: '0.85rem', fontSize: '0.85rem',
}} }}
@@ -192,8 +193,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
border: 'none', border: 'none',
borderRadius: '0.5rem', borderRadius: '0.5rem',
padding: '0.45rem 0.8rem', padding: '0.45rem 0.8rem',
background: '#e5e7eb', background: 'var(--pdf-control)',
color: '#111827', color: 'var(--pdf-control-text)',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.9rem', fontSize: '0.9rem',
}} }}
@@ -207,8 +208,8 @@ const CopyPagesDialog: React.FC<CopyPagesDialogProps> = ({
border: 'none', border: 'none',
borderRadius: '0.5rem', borderRadius: '0.5rem',
padding: '0.45rem 0.8rem', padding: '0.45rem 0.8rem',
background: '#16a34a', background: 'var(--pdf-success-text)',
color: 'white', color: 'var(--pdf-on-primary)',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.9rem', fontSize: '0.9rem',
}} }}

View File

@@ -53,11 +53,11 @@ const PageCard: React.FC<PageCardProps> = ({
}) => { }) => {
const background = isDraggingCard const background = isDraggingCard
? isCopyDragging ? isCopyDragging
? '#dcfce7' ? 'var(--pdf-success-soft)'
: '#dbeafe' : 'var(--pdf-info-surface)'
: selected : selected
? '#eff6ff' ? 'var(--pdf-info-surface)'
: '#f9fafb'; : 'var(--pdf-surface-soft)';
return ( return (
<div <div
@@ -71,7 +71,7 @@ const PageCard: React.FC<PageCardProps> = ({
width: '162px', width: '162px',
padding: '0.4rem', padding: '0.4rem',
borderRadius: '0.5rem', borderRadius: '0.5rem',
border: '1px solid #e5e7eb', border: '1px solid var(--pdf-border)',
background, background,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -91,9 +91,11 @@ const PageCard: React.FC<PageCardProps> = ({
width: '20px', width: '20px',
height: '20px', height: '20px',
borderRadius: '0.4rem', borderRadius: '0.4rem',
border: '1px solid #9ca3af', border: '1px solid var(--pdf-border-strong)',
background: selected ? '#2563eb' : 'rgba(255,255,255,0.9)', background: selected
color: selected ? 'white' : 'transparent', ? 'var(--pdf-primary)'
: 'color-mix(in srgb, var(--pdf-surface) 92%, transparent)',
color: selected ? 'var(--pdf-on-primary)' : 'transparent',
fontSize: '0.8rem', fontSize: '0.8rem',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@@ -131,8 +133,8 @@ const PageCard: React.FC<PageCardProps> = ({
height: 'auto', height: 'auto',
objectFit: 'contain', objectFit: 'contain',
borderRadius: '0.25rem', borderRadius: '0.25rem',
border: '1px solid #e5e7eb', border: '1px solid var(--pdf-paper-border)',
background: 'white', background: 'var(--pdf-paper)',
}} }}
/> />
) : ( ) : (
@@ -141,8 +143,8 @@ const PageCard: React.FC<PageCardProps> = ({
width: '60px', width: '60px',
height: '80px', height: '80px',
borderRadius: '0.25rem', borderRadius: '0.25rem',
border: '1px dashed #d1d5db', border: '1px dashed var(--pdf-paper-border)',
background: '#f3f4f6', background: 'var(--pdf-paper-placeholder)',
}} }}
/> />
)} )}
@@ -151,7 +153,7 @@ const PageCard: React.FC<PageCardProps> = ({
<span style={{ fontSize: '0.8rem' }}> <span style={{ fontSize: '0.8rem' }}>
Page {page.sourcePageIndex + 1} Page {page.sourcePageIndex + 1}
</span> </span>
<span style={{ fontSize: '0.7rem', color: '#6b7280' }}> <span style={{ fontSize: '0.7rem', color: 'var(--pdf-muted-soft)' }}>
Pos {visualIndex + 1} · Rot {page.rotation}° Pos {visualIndex + 1} · Rot {page.rotation}°
</span> </span>
@@ -170,7 +172,8 @@ const PageCard: React.FC<PageCardProps> = ({
}} }}
style={{ style={{
...pageActionButtonStyle, ...pageActionButtonStyle,
background: '#e5e7eb', background: 'var(--pdf-control)',
color: 'var(--pdf-control-text)',
}} }}
> >
90° 90°
@@ -184,7 +187,8 @@ const PageCard: React.FC<PageCardProps> = ({
}} }}
style={{ style={{
...pageActionButtonStyle, ...pageActionButtonStyle,
background: '#e5e7eb', background: 'var(--pdf-control)',
color: 'var(--pdf-control-text)',
}} }}
> >
90° 90°
@@ -198,8 +202,8 @@ const PageCard: React.FC<PageCardProps> = ({
}} }}
style={{ style={{
...pageActionButtonStyle, ...pageActionButtonStyle,
background: '#fecaca', background: 'var(--pdf-danger-soft)',
color: '#b91c1c', color: 'var(--pdf-danger-text)',
}} }}
title="Remove this page from the exported PDF" title="Remove this page from the exported PDF"
> >

View File

@@ -53,8 +53,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
disabled={!hasSelection} disabled={!hasSelection}
style={{ style={{
...pillButtonStyle, ...pillButtonStyle,
background: '#dcfce7', background: 'var(--pdf-success-soft)',
color: '#166534', color: 'var(--pdf-success-text)',
cursor: 'pointer', cursor: 'pointer',
}} }}
title="Copy selected pages to another position" title="Copy selected pages to another position"
@@ -69,8 +69,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
onClick={onDeleteSelected} onClick={onDeleteSelected}
style={{ style={{
...pillButtonStyle, ...pillButtonStyle,
background: '#fee2e2', background: 'var(--pdf-danger-soft)',
color: '#b91c1c', color: 'var(--pdf-danger-text)',
cursor: 'pointer', cursor: 'pointer',
}} }}
> >
@@ -83,8 +83,8 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
onClick={onSelectAll} onClick={onSelectAll}
style={{ style={{
...pillButtonStyle, ...pillButtonStyle,
background: '#8dcd8d', background: 'var(--pdf-success-border)',
color: '#111827', color: 'var(--pdf-control-text)',
cursor: 'pointer', cursor: 'pointer',
}} }}
> >
@@ -97,8 +97,10 @@ const PageSelectionToolbar: React.FC<PageSelectionToolbarProps> = ({
disabled={!hasSelection} disabled={!hasSelection}
style={{ style={{
...pillButtonStyle, ...pillButtonStyle,
background: '#e5e7eb', background: 'var(--pdf-control)',
color: hasSelection ? '#111827' : '#6b7280', color: hasSelection
? 'var(--pdf-control-text)'
: 'var(--pdf-muted-soft)',
cursor: hasSelection ? 'pointer' : 'default', cursor: hasSelection ? 'pointer' : 'default',
}} }}
> >

View File

@@ -279,13 +279,15 @@ const ReorderPanel: React.FC<ReorderPanelProps> = ({
draggingPage != null && draggingPage != null &&
selectedPageIds.length > 0 && selectedPageIds.length > 0 &&
selectedPageIds.includes(draggingPage.id); selectedPageIds.includes(draggingPage.id);
const dropIndicatorColor = isCopyDragging ? '#16a34a' : '#2563eb'; const dropIndicatorColor = isCopyDragging
? 'var(--pdf-success-text)'
: 'var(--pdf-primary)';
return ( return (
<> <>
<div className="card"> <div className="card">
<h2>Pages</h2> <h2>Pages</h2>
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
Tap/click a page to preview it. Use the checkbox to select pages Tap/click a page to preview it. Use the checkbox to select pages
(Shift for ranges). Drag to reorder; dragging a selected page moves (Shift for ranges). Drag to reorder; dragging a selected page moves
the whole selection. Hold Ctrl/ while dropping to copy instead of the whole selection. Hold Ctrl/ while dropping to copy instead of

View File

@@ -54,7 +54,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
<div className="card"> <div className="card">
<h2>Workspace</h2> <h2>Workspace</h2>
<p style={{ fontSize: '0.85rem', color: '#6b7280' }}> <p style={{ fontSize: '0.85rem', color: 'var(--pdf-muted-soft)' }}>
Save named workspaces in this browser. PDF binaries are stored in Save named workspaces in this browser. PDF binaries are stored in
IndexedDB; nothing is uploaded. IndexedDB; nothing is uploaded.
</p> </p>
@@ -78,7 +78,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
minWidth: 0, minWidth: 0,
padding: '0.45rem 0.55rem', padding: '0.45rem 0.55rem',
borderRadius: '0.5rem', borderRadius: '0.5rem',
border: '1px solid #d1d5db', border: '1px solid var(--pdf-border-strong)',
fontSize: '0.9rem', fontSize: '0.9rem',
}} }}
/> />
@@ -140,7 +140,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
style={{ style={{
marginTop: '0.5rem', marginTop: '0.5rem',
fontSize: '0.8rem', fontSize: '0.8rem',
color: '#92400e', color: 'var(--pdf-warning-text)',
}} }}
> >
Unsaved workspace changes. Unsaved workspace changes.
@@ -152,7 +152,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
style={{ style={{
marginTop: '0.5rem', marginTop: '0.5rem',
fontSize: '0.85rem', fontSize: '0.85rem',
color: '#166534', color: 'var(--pdf-success-text)',
}} }}
> >
{workspaceMessage} {workspaceMessage}
@@ -178,10 +178,12 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
<div <div
key={workspace.id} key={workspace.id}
style={{ style={{
border: '1px solid #e5e7eb', border: '1px solid var(--pdf-border)',
borderRadius: '0.5rem', borderRadius: '0.5rem',
padding: '0.5rem', padding: '0.5rem',
background: active ? '#eff6ff' : '#f9fafb', background: active
? 'var(--pdf-info-surface)'
: 'var(--pdf-surface-soft)',
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
gap: '0.75rem', gap: '0.75rem',
@@ -193,11 +195,19 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
<div style={{ fontSize: '0.9rem' }}> <div style={{ fontSize: '0.9rem' }}>
<strong>{workspace.name}</strong> <strong>{workspace.name}</strong>
{active && ( {active && (
<span style={{ color: '#2563eb' }}> · active</span> <span style={{ color: 'var(--pdf-primary)' }}>
{' '}
· active
</span>
)} )}
</div> </div>
<div style={{ fontSize: '0.75rem', color: '#6b7280' }}> <div
style={{
fontSize: '0.75rem',
color: 'var(--pdf-muted-soft)',
}}
>
{workspace.pdfName} · source pages:{' '} {workspace.pdfName} · source pages:{' '}
{workspace.sourcePageCount} · workspace pages:{' '} {workspace.sourcePageCount} · workspace pages:{' '}
{workspace.workspacePageCount} · undo:{' '} {workspace.workspacePageCount} · undo:{' '}
@@ -228,8 +238,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
disabled={isBusy} disabled={isBusy}
onClick={() => onDeleteWorkspace(workspace.id)} onClick={() => onDeleteWorkspace(workspace.id)}
style={{ style={{
background: '#fee2e2', background: 'var(--pdf-danger-soft)',
color: '#991b1b', color: 'var(--pdf-danger-text)',
}} }}
> >
Delete Delete
@@ -261,8 +271,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
key={entry.id} key={entry.id}
style={{ style={{
fontSize: '0.8rem', fontSize: '0.8rem',
color: '#374151', color: 'var(--pdf-muted)',
borderLeft: '3px solid #2563eb', borderLeft: '3px solid var(--pdf-primary)',
paddingLeft: '0.45rem', paddingLeft: '0.45rem',
paddingTop: '0.2rem', paddingTop: '0.2rem',
paddingBottom: '0.2rem', paddingBottom: '0.2rem',
@@ -272,7 +282,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
Undo {history.length - index}. {entry.label} Undo {history.length - index}. {entry.label}
</strong> </strong>
<br /> <br />
<span style={{ color: '#6b7280' }}> <span style={{ color: 'var(--pdf-muted-soft)' }}>
{new Date(entry.timestamp).toLocaleString()} {new Date(entry.timestamp).toLocaleString()}
</span> </span>
</div> </div>
@@ -282,12 +292,12 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
style={{ style={{
margin: '0.25rem 0', margin: '0.25rem 0',
borderRadius: '999px', borderRadius: '999px',
background: '#ecfdf5', background: 'var(--pdf-success-surface)',
color: '#166534', color: 'var(--pdf-success-text)',
fontSize: '0.8rem', fontSize: '0.8rem',
fontWeight: 600, fontWeight: 600,
alignSelf: 'flex-start', alignSelf: 'flex-start',
border: '2px solid #166534', border: '2px solid var(--pdf-success-text)',
width: '100%', width: '100%',
}} }}
></div> ></div>
@@ -300,8 +310,8 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
key={entry.id} key={entry.id}
style={{ style={{
fontSize: '0.8rem', fontSize: '0.8rem',
color: '#9ca3af', color: 'var(--pdf-muted-faint)',
borderLeft: '3px solid #d1d5db', borderLeft: '3px solid var(--pdf-border-strong)',
paddingLeft: '0.45rem', paddingLeft: '0.45rem',
paddingTop: '0.2rem', paddingTop: '0.2rem',
paddingBottom: '0.2rem', paddingBottom: '0.2rem',
@@ -312,7 +322,7 @@ const WorkspacePanel: React.FC<WorkspacePanelProps> = ({
Redo {index + 1}. {entry.label} Redo {index + 1}. {entry.label}
</strong> </strong>
<br /> <br />
<span style={{ color: '#9ca3af' }}> <span style={{ color: 'var(--pdf-muted-faint)' }}>
{new Date(entry.timestamp).toLocaleString()} {new Date(entry.timestamp).toLocaleString()}
</span> </span>
</div> </div>

View File

@@ -12,89 +12,30 @@ body {
BlinkMacSystemFont, BlinkMacSystemFont,
'Segoe UI', 'Segoe UI',
sans-serif; sans-serif;
background-color: #f3f4f6; background-color: var(--toolbox-background);
color: #111827; color: var(--toolbox-text);
} }
#root { #root {
min-height: 100vh; min-height: 100vh;
} }
.app-shell { .pdf-toolbox-shell .toolbox-shell__main .card {
display: flex; border: 1px solid var(--pdf-border);
min-height: 100vh; background: var(--pdf-surface);
}
.app-sidebar {
width: 260px;
background: #111827;
color: #e5e7eb;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.app-sidebar h1 {
font-size: 1.1rem;
margin: 0;
}
.app-sidebar small {
color: #9ca3af;
}
.app-nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.app-nav button {
width: 100%;
text-align: left;
padding: 0.4rem 0.6rem;
border-radius: 0.375rem;
border: none;
background: transparent;
color: #d1d5db;
cursor: pointer;
font-size: 0.9rem;
}
.app-nav button.active {
background: #374151;
color: #f9fafb;
}
.app-nav button:disabled {
opacity: 0.5;
cursor: default;
}
.app-main {
flex: 1;
padding: 1rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.card {
background: white;
border-radius: 0.75rem; border-radius: 0.75rem;
padding: 1rem; padding: 1rem;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12); box-shadow: var(--pdf-card-shadow);
} }
.card h2 { .pdf-toolbox-shell .toolbox-shell__main .card h2 {
margin-top: 0; margin-top: 0;
font-size: 1rem; font-size: 1rem;
} }
button.primary { .pdf-toolbox-shell .toolbox-shell__main button.primary {
background: #2563eb; background: var(--pdf-primary);
color: white; color: var(--pdf-on-primary);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.45rem 0.9rem; padding: 0.45rem 0.9rem;
border: none; border: none;
@@ -102,14 +43,14 @@ button.primary {
font-size: 0.9rem; font-size: 0.9rem;
} }
button.primary:disabled { .pdf-toolbox-shell .toolbox-shell__main button.primary:disabled {
opacity: 0.6; opacity: 0.6;
cursor: default; cursor: default;
} }
button.secondary { .pdf-toolbox-shell .toolbox-shell__main button.secondary {
background: #e5e7eb; background: var(--pdf-control);
color: #111827; color: var(--pdf-control-text);
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.45rem 0.9rem; padding: 0.45rem 0.9rem;
border: none; border: none;
@@ -117,7 +58,7 @@ button.secondary {
font-size: 0.9rem; font-size: 0.9rem;
} }
.button-row { .pdf-toolbox-shell .toolbox-shell__main .button-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5rem; gap: 0.5rem;
@@ -125,14 +66,104 @@ button.secondary {
} }
.pdf-toolbox-shell { .pdf-toolbox-shell {
color-scheme: light; --pdf-surface: var(--toolbox-surface);
--toolbox-background: #f3f4f6; --pdf-surface-soft: #f9fafb;
--toolbox-surface: #ffffff; --pdf-surface-raised: #ffffff;
--toolbox-text: #111827; --pdf-text: var(--toolbox-text);
--toolbox-muted: #4b5563; --pdf-muted: #4b5563;
--toolbox-border: #d1d5db; --pdf-muted-soft: #6b7280;
--toolbox-accent: #2563eb; --pdf-muted-faint: #9ca3af;
--toolbox-accent-contrast: #ffffff; --pdf-border: #e5e7eb;
--pdf-border-strong: #d1d5db;
--pdf-primary: #2563eb;
--pdf-on-primary: #ffffff;
--pdf-control: #e5e7eb;
--pdf-control-text: #111827;
--pdf-info-surface: #eff6ff;
--pdf-info-border: #bfdbfe;
--pdf-info-text: #1e3a8a;
--pdf-success-surface: #ecfdf5;
--pdf-success-soft: #dcfce7;
--pdf-success-border: #86efac;
--pdf-success-text: #166534;
--pdf-warning-surface: #fff7ed;
--pdf-warning-border: #fed7aa;
--pdf-warning-text: #9a3412;
--pdf-danger-surface: #fef2f2;
--pdf-danger-soft: #fee2e2;
--pdf-danger-border: #fecaca;
--pdf-danger-text: #b91c1c;
--pdf-overlay: rgba(15, 23, 42, 0.62);
--pdf-dialog-shadow: 0 20px 40px rgba(15, 23, 42, 0.35);
--pdf-card-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);
--pdf-paper: #ffffff;
--pdf-paper-placeholder: #f3f4f6;
--pdf-paper-border: #d1d5db;
--pdf-paper-text: #111827;
}
.pdf-toolbox-shell[data-toolbox-theme='dark'] {
--pdf-surface-soft: #20283a;
--pdf-surface-raised: #1b2334;
--pdf-muted: #b6bfd2;
--pdf-muted-soft: #a9b3ca;
--pdf-muted-faint: #8792aa;
--pdf-border: #303a50;
--pdf-border-strong: #465169;
--pdf-primary: #8798f5;
--pdf-on-primary: #101728;
--pdf-control: #293247;
--pdf-control-text: #edf1fb;
--pdf-info-surface: #182946;
--pdf-info-border: #345a8c;
--pdf-info-text: #c6dcff;
--pdf-success-surface: #12382e;
--pdf-success-soft: #174436;
--pdf-success-border: #33765d;
--pdf-success-text: #a7e8cf;
--pdf-warning-surface: #402814;
--pdf-warning-border: #80522d;
--pdf-warning-text: #ffd2a6;
--pdf-danger-surface: #421f29;
--pdf-danger-soft: #4f2530;
--pdf-danger-border: #81394b;
--pdf-danger-text: #ffc0cc;
--pdf-overlay: rgba(2, 6, 16, 0.76);
--pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5);
--pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26);
}
@media (prefers-color-scheme: dark) {
.pdf-toolbox-shell[data-toolbox-theme='system'] {
--pdf-surface-soft: #20283a;
--pdf-surface-raised: #1b2334;
--pdf-muted: #b6bfd2;
--pdf-muted-soft: #a9b3ca;
--pdf-muted-faint: #8792aa;
--pdf-border: #303a50;
--pdf-border-strong: #465169;
--pdf-primary: #8798f5;
--pdf-on-primary: #101728;
--pdf-control: #293247;
--pdf-control-text: #edf1fb;
--pdf-info-surface: #182946;
--pdf-info-border: #345a8c;
--pdf-info-text: #c6dcff;
--pdf-success-surface: #12382e;
--pdf-success-soft: #174436;
--pdf-success-border: #33765d;
--pdf-success-text: #a7e8cf;
--pdf-warning-surface: #402814;
--pdf-warning-border: #80522d;
--pdf-warning-text: #ffd2a6;
--pdf-danger-surface: #421f29;
--pdf-danger-soft: #4f2530;
--pdf-danger-border: #81394b;
--pdf-danger-text: #ffc0cc;
--pdf-overlay: rgba(2, 6, 16, 0.76);
--pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5);
--pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26);
}
} }
.pdf-toolbox-shell .toolbox-shell__main { .pdf-toolbox-shell .toolbox-shell__main {
@@ -143,46 +174,59 @@ button.secondary {
gap: 0.75rem; gap: 0.75rem;
} }
/* Make cards full-width on mobile */ .pdf-toolbox-shell .toolbox-shell__main input,
.card { .pdf-toolbox-shell .toolbox-shell__main select,
.pdf-toolbox-shell .toolbox-shell__main textarea {
border-color: var(--pdf-border-strong);
background: var(--pdf-surface);
color: var(--pdf-text);
}
.pdf-toolbox-shell .toolbox-shell__main input::placeholder,
.pdf-toolbox-shell .toolbox-shell__main textarea::placeholder {
color: var(--pdf-muted-soft);
}
.pdf-toolbox-shell .toolbox-shell__main a {
color: var(--toolbox-accent);
}
.pdf-toolbox-shell .toolbox-shell__main a:hover {
color: var(--toolbox-accent-hover);
}
.pdf-toolbox-shell .toolbox-shell__main button:disabled {
opacity: 0.58;
}
.pdf-toolbox-shell .toolbox-shell__main button:focus-visible,
.pdf-toolbox-shell .toolbox-shell__main a:focus-visible,
.pdf-toolbox-shell .toolbox-shell__main input:focus-visible,
.pdf-toolbox-shell .toolbox-shell__main select:focus-visible {
outline: 3px solid color-mix(in srgb, var(--toolbox-focus) 72%, transparent);
outline-offset: 2px;
}
.pdf-toolbox-shell .toolbox-shell__main .card {
width: 100%; width: 100%;
} }
.download-link { .pdf-toolbox-shell .toolbox-shell__main .download-link {
display: inline-block; display: inline-block;
margin: 0.15rem 0; margin: 0.15rem 0;
font-size: 0.85rem; font-size: 0.85rem;
} }
.card hr { .pdf-toolbox-shell .toolbox-shell__main .card hr {
border: none; border: none;
border-top: 1px solid #e5e7eb; border-top: 1px solid var(--pdf-border);
}
.app-help-button {
border: 1px solid var(--toolbox-border);
border-radius: 999px;
background: transparent;
color: var(--toolbox-accent);
padding: 0.35rem 0.65rem;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
line-height: 1;
}
.app-help-button:hover,
.app-help-button:focus-visible {
background: color-mix(in srgb, var(--toolbox-accent) 10%, transparent);
outline: 2px solid var(--toolbox-accent);
outline-offset: 2px;
} }
.help-dialog-backdrop { .help-dialog-backdrop {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 80; z-index: 80;
background: rgba(15, 23, 42, 0.65); background: var(--pdf-overlay);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -193,9 +237,9 @@ button.secondary {
width: min(920px, 100%); width: min(920px, 100%);
max-height: min(88vh, 760px); max-height: min(88vh, 760px);
overflow: auto; overflow: auto;
background: #ffffff; background: var(--pdf-surface-raised);
border-radius: 0.9rem; border-radius: 0.9rem;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.4); box-shadow: var(--pdf-dialog-shadow);
} }
.help-dialog-header { .help-dialog-header {
@@ -207,8 +251,8 @@ button.secondary {
align-items: flex-start; align-items: flex-start;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
background: #ffffff; background: var(--pdf-surface-raised);
border-bottom: 1px solid #e5e7eb; border-bottom: 1px solid var(--pdf-border);
} }
.help-dialog-header h2 { .help-dialog-header h2 {
@@ -218,7 +262,7 @@ button.secondary {
.help-dialog-header p { .help-dialog-header p {
margin: 0.35rem 0 0; margin: 0.35rem 0 0;
color: #4b5563; color: var(--pdf-muted);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.45; line-height: 1.45;
} }
@@ -229,8 +273,8 @@ button.secondary {
border-radius: 999px; border-radius: 999px;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
background: #e5e7eb; background: var(--pdf-control);
color: #111827; color: var(--pdf-control-text);
cursor: pointer; cursor: pointer;
font-size: 1.25rem; font-size: 1.25rem;
line-height: 1; line-height: 1;
@@ -244,10 +288,10 @@ button.secondary {
} }
.help-section { .help-section {
border: 1px solid #e5e7eb; border: 1px solid var(--pdf-border);
border-radius: 0.75rem; border-radius: 0.75rem;
padding: 0.9rem; padding: 0.9rem;
background: #f9fafb; background: var(--pdf-surface-soft);
} }
.help-section h3 { .help-section h3 {
@@ -263,8 +307,8 @@ button.secondary {
.help-step { .help-step {
border-radius: 0.65rem; border-radius: 0.65rem;
background: #ffffff; background: var(--pdf-surface);
border: 1px solid #e5e7eb; border: 1px solid var(--pdf-border);
padding: 0.75rem; padding: 0.75rem;
} }
@@ -276,7 +320,7 @@ button.secondary {
.help-step p, .help-step p,
.help-note { .help-note {
margin: 0.35rem 0 0; margin: 0.35rem 0 0;
color: #4b5563; color: var(--pdf-muted);
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.45; line-height: 1.45;
} }
@@ -292,18 +336,18 @@ button.secondary {
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
border-radius: 0.4rem; border-radius: 0.4rem;
border: 1px solid #d1d5db; border: 1px solid var(--pdf-border-strong);
background: #ffffff; background: var(--pdf-surface);
padding: 0.2rem 0.45rem; padding: 0.2rem 0.45rem;
font-size: 0.78rem; font-size: 0.78rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
color: #111827; color: var(--pdf-text);
box-shadow: inset 0 -1px 0 #d1d5db; box-shadow: inset 0 -1px 0 var(--pdf-border-strong);
white-space: nowrap; white-space: nowrap;
} }
.shortcut-grid span { .shortcut-grid span {
color: #4b5563; color: var(--pdf-muted);
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.35; line-height: 1.35;
} }
@@ -320,8 +364,8 @@ button.secondary {
} }
.help-concepts dl > div { .help-concepts dl > div {
background: #ffffff; background: var(--pdf-surface);
border: 1px solid #e5e7eb; border: 1px solid var(--pdf-border);
border-radius: 0.65rem; border-radius: 0.65rem;
padding: 0.75rem; padding: 0.75rem;
} }
@@ -334,7 +378,7 @@ button.secondary {
.help-concepts dd { .help-concepts dd {
margin: 0; margin: 0;
color: #4b5563; color: var(--pdf-muted);
font-size: 0.82rem; font-size: 0.82rem;
line-height: 1.4; line-height: 1.4;
} }
@@ -358,16 +402,16 @@ button.secondary {
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
border: 1px solid #bfdbfe; border: 1px solid var(--pdf-info-border);
border-radius: 0.75rem; border-radius: 0.75rem;
background: rgba(255, 255, 255, 0.8); background: color-mix(in srgb, var(--pdf-surface) 86%, transparent);
padding: 0.6rem; padding: 0.6rem;
} }
.merge-queue-order { .merge-queue-order {
border-radius: 999px; border-radius: 999px;
background: #dbeafe; background: var(--pdf-info-surface);
color: #1e3a8a; color: var(--pdf-info-text);
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 700; font-weight: 700;
padding: 0.2rem 0.5rem; padding: 0.2rem 0.5rem;
@@ -386,7 +430,7 @@ button.secondary {
} }
.merge-queue-details span { .merge-queue-details span {
color: #4b5563; color: var(--pdf-muted);
font-size: 0.8rem; font-size: 0.8rem;
} }
@@ -418,10 +462,10 @@ button.secondary {
.merge-warning { .merge-warning {
margin: 0.75rem 0 0; margin: 0.75rem 0 0;
border: 1px solid #fed7aa; border: 1px solid var(--pdf-warning-border);
border-radius: 0.5rem; border-radius: 0.5rem;
background: #fff7ed; background: var(--pdf-warning-surface);
color: #9a3412; color: var(--pdf-warning-text);
padding: 0.55rem 0.65rem; padding: 0.55rem 0.65rem;
font-size: 0.85rem; font-size: 0.85rem;
} }

View File

@@ -6,13 +6,6 @@
"icon": "./favicon.svg", "icon": "./favicon.svg",
"categories": ["documents", "pdf"], "categories": ["documents", "pdf"],
"tags": ["merge", "split", "rotate", "reorder"], "tags": ["merge", "split", "rotate", "reorder"],
"actions": [
{
"id": "source",
"label": "Source",
"url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4"
}
],
"integration": { "integration": {
"contextVersion": 1, "contextVersion": 1,
"launchModes": ["navigate", "new-tab"], "launchModes": ["navigate", "new-tab"],

View File

@@ -7,4 +7,11 @@ describe('toolbox app definition', () => {
it('matches the generated public manifest', () => { it('matches the generated public manifest', () => {
expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest)); expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest));
}); });
it('uses repository metadata for the shared source control', () => {
expect(toolboxApp.source?.repository).toBe(
'https://git.add-ideas.de/zemion/pdf-tools'
);
expect(toolboxApp.actions).toBeUndefined();
});
}); });

View File

@@ -1 +1 @@
export const APP_VERSION = '0.3.4'; export const APP_VERSION = '0.4.1';