diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 2dd4402f..e9009c0a 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -17,6 +17,11 @@ on: - 'frontend/**' - '.github/workflows/frontend-tests.yml' workflow_dispatch: + inputs: + update-snapshots: + description: 'Regenerate Playwright visual snapshots and commit them to the branch' + type: boolean + default: false # Least privilege: these jobs only need to read the repo to check it out. # Artifact upload uses the separate ACTIONS_RUNTIME_TOKEN, not GITHUB_TOKEN, @@ -46,6 +51,9 @@ jobs: e2e: name: E2E tests (Playwright) + # Skipped when a dispatch asks for snapshot regeneration instead — running + # both would fail this job against the stale snapshots being replaced. + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.update-snapshots) }} timeout-minutes: 60 runs-on: ubuntu-latest # Pinned to match the installed @playwright/test version (1.60.0); bump both @@ -70,3 +78,42 @@ jobs: name: playwright-report path: frontend/playwright-report/ retention-days: 30 + + # Visual snapshots must be rendered in the exact CI environment (container + # image, fonts, GPU rasterization) to be pixel-stable, so regeneration runs + # here rather than on a developer machine. Trigger manually: + # gh workflow run frontend-tests.yml --ref -f update-snapshots=true + # The job commits the refreshed PNGs back to the triggering branch. + update-snapshots: + name: Regenerate visual snapshots + if: ${{ github.event_name == 'workflow_dispatch' && inputs.update-snapshots }} + timeout-minutes: 60 + runs-on: ubuntu-latest + permissions: + contents: write + container: + image: mcr.microsoft.com/playwright:v1.60.0-noble + options: --user 1001 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Build Frontend + run: npm run build + - name: Regenerate snapshots + run: npx playwright test --update-snapshots + - name: Commit refreshed snapshots + working-directory: . + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add frontend/tests/*-snapshots + if git diff --cached --quiet; then + echo "Snapshots unchanged; nothing to commit." + else + git commit -m "Regenerate Playwright visual snapshots" + git push origin HEAD:"$GITHUB_REF_NAME" + fi diff --git a/VISUAL_REGRESSION.md b/VISUAL_REGRESSION.md index bb0087a0..ae115947 100644 --- a/VISUAL_REGRESSION.md +++ b/VISUAL_REGRESSION.md @@ -2,12 +2,12 @@ This project uses Playwright to run visual regression tests that capture screenshots and compare them against baseline images. Currently, we have tests set up only for the demo page of the application. -## Running the tests +The tests run as part of the **Frontend Tests** workflow (`.github/workflows/frontend-tests.yml`), in the `e2e` job. Only the Chromium project is enabled in `frontend/playwright.config.ts`, so there is a single baseline: -1. Navigate to the **Actions** tab in GitHub -2. Select **Playwright Visual Regression Tests** workflow -3. Click **Run workflow** button and choose the branch you wish to run tests on -4. The workflow will run tests against all browsers (Chromium, Firefox, WebKit) +``` +frontend/tests/demo-page-visual.spec.ts-snapshots/ +└── demo-page-chromium-linux.png +``` ## Understanding test results @@ -28,23 +28,16 @@ When tests fail: ## Updating baseline images -If the UI changes are **intentional** and you want to update the baselines: +Baselines are only pixel-stable when rendered in CI's pinned Playwright container, so don't regenerate them on your own machine. If the UI changes are **intentional**, use the regeneration job: -1. From the Playwright report, download the actual images for each browser -2. Replace the existing baseline images in `frontend/tests/demo-page-visual.spec.ts-snapshots/` -3. Rename downloaded images to match existing baseline names: - - `demo-page-chromium-linux.png` - - `demo-page-firefox-linux.png` - - `demo-page-webkit-linux.png` -4. Commit and push the updated baseline images -5. Re-run the visual regression test to verify it passes +1. Navigate to the **Actions** tab in GitHub and select the **Frontend Tests** workflow +2. Click **Run workflow**, choose your branch, and check **Regenerate Playwright visual snapshots** +3. The `update-snapshots` job re-renders the baselines in the same container the tests use and commits them back to your branch -## Baseline image locations +Or from the CLI: -Current baseline images are stored in: -``` -frontend/tests/demo-page-visual.spec.ts-snapshots/ -├── demo-page-chromium-linux.png -├── demo-page-firefox-linux.png -└── demo-page-webkit-linux.png +```sh +gh workflow run frontend-tests.yml --ref -f update-snapshots=true ``` + +Alternatively, you can download the actual image from a failed run's playwright-report artifact, rename it to `demo-page-chromium-linux.png`, and commit it in place of the baseline. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b47e9f6f..7a680a7a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,9 @@ "dependencies": { "@ai-sdk/openai": "^2.0.0", "@auth0/auth0-react": "^2.2.4", + "@fontsource-variable/dm-sans": "^5.2.8", + "@fontsource-variable/source-serif-4": "^5.2.9", + "@fontsource/dm-mono": "^5.2.7", "@lexical/react": "^0.16.1", "@posthog/react": "^1.9.0", "@react-hook/window-size": "^3.1.1", @@ -2917,6 +2920,33 @@ "node": ">= 10" } }, + "node_modules/@fontsource-variable/dm-sans": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/dm-sans/-/dm-sans-5.2.8.tgz", + "integrity": "sha512-AxkvMTvNWgfrmlyjiV05vlHYJa+nRQCf1EfvIrQAPBpFJW0O9VTz7oAFr9S3lvbWdmnFoBk7yFqQL86u64nl2g==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/source-serif-4": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@fontsource-variable/source-serif-4/-/source-serif-4-5.2.9.tgz", + "integrity": "sha512-PPcxjLFk/fS0WHg79pDM2YNvz61kC+oYZ5cWZZyCS0DHpJncmuYOuiZAsvj4tDxlWPBEvxxcRLQQNmSaRbPkqw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/dm-mono": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/dm-mono/-/dm-mono-5.2.7.tgz", + "integrity": "sha512-Ma1az2atTVgQWuOWwjuxx26p/6A6CU9HBNKq1CFV6YKpKhpswnf9ry9Ql4+T6bTZzkdtSfS6tjJvqZOljVzIFQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@hono/node-server": { "version": "1.19.14", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", @@ -24677,6 +24707,24 @@ } } }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/vscode-jsonrpc": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4485fcd0..30ca987f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,6 +47,9 @@ "dependencies": { "@ai-sdk/openai": "^2.0.0", "@auth0/auth0-react": "^2.2.4", + "@fontsource-variable/dm-sans": "^5.2.8", + "@fontsource-variable/source-serif-4": "^5.2.9", + "@fontsource/dm-mono": "^5.2.7", "@lexical/react": "^0.16.1", "@posthog/react": "^1.9.0", "@react-hook/window-size": "^3.1.1", diff --git a/frontend/src/components/navbar/index.tsx b/frontend/src/components/navbar/index.tsx index 05147e89..12246482 100644 --- a/frontend/src/components/navbar/index.tsx +++ b/frontend/src/components/navbar/index.tsx @@ -1,47 +1,73 @@ import { - PageName, - pageNameAtom, -} from '@/contexts/pageContext'; + AiOutlineAudit, + AiOutlineEdit, + AiOutlineMessage, +} from 'react-icons/ai'; +import type { IconType } from 'react-icons'; + +import { PageName, pageNameAtom } from '@/contexts/pageContext'; import { useAtom } from 'jotai'; import classes from './styles.module.css'; -/** - * An array of objects representing the names and titles of pages. - * Each object contains the following properties: - * - * @property {PageName} name - The name identifier of the page. - * @property {string} title - The display title of the page. - */ - type Page = { name: PageName; title: string; hint: string; + icon: IconType; }; const pageNames: Page[] = [ - { name: PageName.Draft, title: 'Draft', hint: 'Generate suggestions' }, - { name: PageName.Revise, title: 'Revise', hint: 'Improve your text' }, - { name: PageName.Chat, title: 'Chat', hint: 'Ask about your doc' }, + { + name: PageName.Draft, + title: 'Draft', + hint: 'Generate suggestions', + icon: AiOutlineEdit, + }, + { + name: PageName.Revise, + title: 'Revise', + hint: 'Improve your text', + icon: AiOutlineAudit, + }, + { + name: PageName.Chat, + title: 'Chat', + hint: 'Ask about your doc', + icon: AiOutlineMessage, + }, ]; export default function Navbar() { const [page, changePage] = useAtom(pageNameAtom); return ( -
- {pageNames.map(({ name: pageName, title: pageTitle, hint }) => ( - - ))} -
- ); +
+
+ + Thoughtful + + AI that helps you think + +
+ + +
+ ); } diff --git a/frontend/src/components/navbar/styles.module.css b/frontend/src/components/navbar/styles.module.css index 7eff77fd..a9fe9351 100644 --- a/frontend/src/components/navbar/styles.module.css +++ b/frontend/src/components/navbar/styles.module.css @@ -1,46 +1,100 @@ +.header { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px 14px; + background: var(--surface); + border-bottom: 1px solid var(--border); + font-family: var(--font-ui); +} + +.brandRow { + display: flex; + align-items: baseline; + gap: 7px; + min-width: 0; +} + +.brandMark { + color: var(--accent); + font-size: 14px; + line-height: 1; + align-self: center; +} + +.wordmark { + font-family: var(--font-display); + font-size: 17px; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--text-primary); +} + +.brandTagline { + margin-left: auto; + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--text-tertiary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .tabs { display: flex; - padding: 12px 12px 0; - background: #ffffff; - border-bottom: 1px solid #e4e0d8; - gap: 4px; - flex-shrink: 0; + gap: 3px; + padding: 3px; + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: var(--radius); } .tabBtn { flex: 1; display: flex; - flex-direction: column; align-items: center; - gap: 2px; - padding: 8px 10px 10px; - border: 1px solid transparent; - border-bottom: none; - border-radius: 6px 6px 0 0; + justify-content: center; + gap: 6px; + padding: 7px 6px; + border: none; + border-radius: var(--radius-sm); background: transparent; - font-size: 13px; + font-family: var(--font-ui); + font-size: 12.5px; font-weight: 500; - color: #b3aba0; + color: var(--text-tertiary); cursor: pointer; - font-family: 'DM Sans', sans-serif; - transition: all 0.15s; - margin-bottom: -1px; + transition: + color 0.15s, + background 0.15s, + box-shadow 0.15s; } .tabBtn:hover { - color: #6b6b6b; + color: var(--text-secondary); } -.active { - border-color: #e4e0d8; - border-bottom-color: #fafaf8; - background: #fafaf8; - color: #1c1c1c; +.tabBtn:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--accent-border); +} + +.active, +.active:hover { + background: var(--surface); + color: var(--text-primary); + box-shadow: var(--shadow-sm); +} + +.tabIcon { + width: 14px; + height: 14px; + flex-shrink: 0; } -.tabHint { - font-size: 10px; - font-weight: 400; - color: inherit; - opacity: 0.7; +.active .tabIcon { + color: var(--accent); } diff --git a/frontend/src/editor/styles.module.css b/frontend/src/editor/styles.module.css index c88178bd..41b65022 100644 --- a/frontend/src/editor/styles.module.css +++ b/frontend/src/editor/styles.module.css @@ -1,7 +1,7 @@ /* Container for the editor and sidebar in editor page */ .container { justify-content: space-evenly; - font-family: 'Segoe UI', sans-serif, Arial, Helvetica; + font-family: var(--font-ui, 'Segoe UI', sans-serif); display: flex; flex-direction: row; } @@ -18,8 +18,10 @@ min-width: 14rem; margin: 20px 0px 20px 0px; height: 80vh; - box-shadow: -2px 0 5px rgba(0, 0, 0, 0.3); - font-family: 'Segoe UI', sans-serif, Arial, Helvetica; + border: 1px solid var(--border, #e7e2d6); + border-radius: 12px; + box-shadow: var(--shadow-md, 0 2px 10px rgba(35, 32, 25, 0.08)); + font-family: var(--font-ui, 'Segoe UI', sans-serif); right: 0; background-color: white; overflow: hidden; @@ -32,7 +34,7 @@ .democontainer { display: flex; justify-content: center; - font-family: 'Segoe UI', sans-serif, Arial, Helvetica; + font-family: var(--font-ui, 'Segoe UI', sans-serif); } .demoeditor { @@ -48,8 +50,10 @@ height: 80vh; width: 28rem; min-width: 28rem; - box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); - font-family: 'Segoe UI', sans-serif, Arial, Helvetica; + border: 1px solid var(--border, #e7e2d6); + border-radius: 12px; + box-shadow: var(--shadow-md, 0 2px 10px rgba(35, 32, 25, 0.08)); + font-family: var(--font-ui, 'Segoe UI', sans-serif); background-color: white; overflow: hidden; display: flex; diff --git a/frontend/src/pages/app/index.tsx b/frontend/src/pages/app/index.tsx index f5ecba5c..eb3c1bf0 100644 --- a/frontend/src/pages/app/index.tsx +++ b/frontend/src/pages/app/index.tsx @@ -24,6 +24,11 @@ import classes from './styles.module.css'; import Navbar from '@/components/navbar'; import { Reshaped, Button } from 'reshaped'; import 'reshaped/themes/slate/theme.css'; +import '@fontsource-variable/dm-sans'; +import '@fontsource/dm-mono/400.css'; +import '@fontsource/dm-mono/500.css'; +import '@fontsource-variable/source-serif-4'; +import '@/theme.css'; // PostHog configuration - project token is safe to commit publicly const POSTHOG_KEY = 'phc_p3Br0zRnw7PdTVpdNI92vvBTWcBBY0jvkHO8dNvkCTl'; @@ -79,15 +84,17 @@ function AppInner() { if (isLoading) return (
-
Waiting for authentication
+
+ Waiting for authentication… +
); if (error) return ( -
+

Oops... {error.message}

+
+ <_GenerationResult generation={savedItem.generation} />
+
))}
@@ -413,96 +393,88 @@ export default function Draft() { return (
-
- -
-
- {/* Instruction */} -
- CLICK A DESIRED BUTTON -
+
+ What would help right now? +
- {/* Feature Grid */} -
- {modesToShow.map((mode) => { - const isActive = activeMode === mode; - const Icon = iconFunc(mode); - const meta = modeMeta[mode]; - - return ( - - ); - })} -
- {/* Results Area */} -
0 ? classes.hasContent : ''}`}> - {errorMsg ? ( -
- {errorMsg} -
- ) : null} - {!errorMsg && savedItems.length === 0 && !isLoading ? ( -
-
No suggestions yet
-
- Click a button above to generate suggestions for your text -
-
- ) : null} - {isLoading && savedItems.length === 0 ? ( -
-
-
-
-
- ) : null} - {savedItems.length > 0 ? ( - + {/* Feature Grid */} +
+ {modesToShow.map((mode) => { + const isActive = activeMode === mode; + const Icon = iconFunc(mode); + const meta = modeMeta[mode]; + + return ( + + ); + })} +
+ + {/* Results Area */} +
0 ? classes.hasContent : ''}`}> + {errorMsg ? ( +
{errorMsg}
+ ) : null} + {!errorMsg && savedItems.length === 0 && !isLoading ? ( +
+
No suggestions yet
+
+ Pick a card above and Thoughtful will respond to what + you have written so far
- -
- Please note that AI suggestions may vary in quality. Always review suggestions carefully before using them. + ) : null} + {isLoading && savedItems.length === 0 ? ( +
+
+
+
-
+ ) : null} + {savedItems.length > 0 ? ( + + ) : null} +
+
+ Suggestions are sparks, not answers — the words that matter are + yours. Review anything you use.
- ); - } - + ); +} + diff --git a/frontend/src/pages/draft/styles.module.css b/frontend/src/pages/draft/styles.module.css index c59a8bbf..f6cece5e 100644 --- a/frontend/src/pages/draft/styles.module.css +++ b/frontend/src/pages/draft/styles.module.css @@ -1,108 +1,36 @@ -/* Design Tokens */ -:root { - --bg: #F7F6F2; - --surface: #FFFFFF; - --surface-2: #F0EEE9; - --border: rgba(0,0,0,0.08); - --border-strong: rgba(0,0,0,0.15); - --text-primary: #1A1916; - --text-secondary: #6B6860; - --text-tertiary: #A09D96; - --accent: #3D3BCC; - --accent-bg: #EEEEFF; - --accent-border: #C4C3F5; - --radius: 10px; - --radius-sm: 6px; -} +/* Draft page — design tokens come from src/theme.css */ .app { width: 100%; - max-width: 480px; - background: var(--surface); - overflow: hidden; - box-shadow: 0 4px 24px rgba(0,0,0,0.07); - margin: 0 auto; - display: flex; - flex-direction: column; height: 100%; -} - -.body { - padding: 16px; - overflow-y: auto; - flex: 1; - min-height: 0; - padding-bottom: 24px; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* Page Layout */ -.pageContainer { display: flex; flex-direction: column; - height: 100%; + gap: 10px; + padding: 14px; background: var(--bg); color: var(--text-primary); - font-family: 'DM Sans', sans-serif; + font-family: var(--font-ui); + overflow: hidden; } -/* Tab Bar */ -.tabBar { +.sectionLabel { display: flex; - gap: 2px; - background: var(--surface-2); - border: 0.5px solid var(--border); - border-radius: var(--radius); - padding: 4px; - margin-bottom: 24px; -} - -.tab { - flex: 1; - padding: 8px; - border-radius: var(--radius-sm); - font-size: 13px; - font-weight: 400; - color: var(--text-tertiary); - text-align: center; - cursor: pointer; - transition: all .18s; - border: none; - background: transparent; - font-family: 'DM Sans', sans-serif; -} - -.tab:active { - background: var(--surface); - color: var(--text-primary); + align-items: center; + gap: 10px; + font-family: var(--font-mono); + font-size: 10px; font-weight: 500; - box-shadow: 0 1px 3px rgba(0,0,0,0.08); -} - -.tabDesc { - font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; color: var(--text-tertiary); - margin-top: 2px; - font-weight: 400; + flex-shrink: 0; } -.tab:active .tabDesc { - color: var(--text-secondary); -} - -/* Instruction Line */ -.instruction { - font-family: 'DM Mono', monospace; - font-size: 10px; - letter-spacing: .08em; - color: var(--text-tertiary); - text-transform: uppercase; - margin-bottom: 12px; +.sectionLabel::after { + content: ''; + flex: 1; + height: 1px; + background: var(--border); } /* Feature Grid */ @@ -110,31 +38,36 @@ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; - margin-bottom: 20px; + flex-shrink: 0; } .featureCard { background: var(--surface); - border: 0.5px solid var(--border); + border: 1px solid var(--border); border-radius: var(--radius); - padding: 14px; + padding: 12px; cursor: pointer; - transition: border .15s, background .15s, transform .1s, box-shadow .15s; + transition: + border-color 0.15s, + background 0.15s, + transform 0.1s, + box-shadow 0.15s; text-align: left; - font-family: 'DM Sans', sans-serif; + font-family: var(--font-ui); position: relative; overflow: hidden; outline: none; + box-shadow: var(--shadow-sm); } .featureCard:hover:not(:disabled) { border-color: var(--border-strong); - background: #FAFAF8; transform: translateY(-1px); + box-shadow: var(--shadow-md); } .featureCard:focus-visible { - box-shadow: 0 0 0 2px var(--accent), 0 0 0 4px var(--accent-bg); + box-shadow: 0 0 0 2px var(--accent-border); } .featureCard.active { @@ -143,11 +76,12 @@ } .featureCard.active .featName { - color: var(--accent); + color: var(--accent-strong); } .featureCard.active .featDesc { - color: #7B79D4; + color: var(--accent); + opacity: 0.75; } .featureCard.active .featIcon { @@ -156,32 +90,35 @@ } .featureCard:disabled { - opacity: .6; - cursor: not-allowed; + opacity: 0.55; + cursor: progress; } .featIcon { - width: 18px; - height: 18px; + width: 17px; + height: 17px; margin-bottom: 8px; - opacity: .4; - transition: opacity .15s, color .15s; + opacity: 0.45; + color: var(--text-secondary); + transition: + opacity 0.15s, + color 0.15s; display: block; } .featName { font-size: 13px; - font-weight: 500; + font-weight: 600; color: var(--text-primary); - margin-bottom: 3px; - transition: color .15s; + margin-bottom: 2px; + transition: color 0.15s; } .featDesc { font-size: 11px; color: var(--text-tertiary); line-height: 1.4; - transition: color .15s; + transition: color 0.15s; } .activeDot { @@ -193,8 +130,7 @@ border-radius: 50%; background: var(--accent); opacity: 0; - transition: opacity .15s; - box-shadow: 0 0 4px rgba(61, 59, 204, .3); + transition: opacity 0.15s; } .featureCard.active .activeDot { @@ -203,42 +139,39 @@ /* Results Area */ .resultsArea { + flex: 1; + min-height: 0; + overflow-y: auto; background: var(--surface); - border: 0.5px solid var(--border); + border: 1px solid var(--border); border-radius: var(--radius); - min-height: 180px; display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 32px 24px; - transition: all .2s; - position: relative; - overflow: hidden; + padding: 28px 20px; + box-shadow: var(--shadow-sm); + scrollbar-width: thin; + scrollbar-color: var(--border-strong) transparent; } .resultsArea.hasContent { - align-items: flex-start; + align-items: stretch; justify-content: flex-start; - padding: 16px; - min-height: auto; + padding: 10px; } -.emptyIcon { - width: 24px; - height: 24px; - opacity: .2; - margin-bottom: 12px; - animation: fadeIn .3s ease both; +.emptyStateContainer { + text-align: center; } .emptyTitle { - font-size: 14px; - font-weight: 500; - color: var(--text-primary); + font-family: var(--font-display); + font-size: 15px; + font-weight: 600; + color: var(--text-secondary); margin-bottom: 6px; - opacity: .4; - animation: fadeIn .3s ease both .1s; + animation: fadeIn 0.3s ease both; } .emptyHint { @@ -246,53 +179,75 @@ color: var(--text-tertiary); text-align: center; line-height: 1.5; - max-width: 260px; - animation: fadeIn .3s ease both .2s; + max-width: 240px; + margin: 0 auto; + animation: fadeIn 0.3s ease both 0.1s; +} + +.errorMessage { + width: 100%; + font-size: 12.5px; + line-height: 1.5; + color: var(--danger); + background: #faf0ef; + border: 1px solid #ecd4d1; + border-radius: var(--radius-sm); + padding: 10px 12px; + margin-bottom: 8px; } -.resultHeader { +/* Saved suggestions */ +.savedList { display: flex; - align-items: center; + flex-direction: column; gap: 8px; - margin-bottom: 12px; width: 100%; - padding-bottom: 10px; - border-bottom: 0.5px solid var(--border); } -.resultTag { - font-size: 11px; - font-weight: 500; - padding: 3px 10px; - border-radius: 20px; - background: var(--accent-bg); - color: var(--accent); - border: 0.5px solid var(--accent-border); - font-family: 'DM Sans', sans-serif; +.resultItem { + display: flex; + align-items: flex-start; + gap: 8px; + width: 100%; + padding: 12px 14px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + animation: fadeUp 0.3s ease both; } -.resultMeta { - font-size: 11px; - color: var(--text-tertiary); - margin-left: auto; - font-family: 'DM Mono', monospace; +.resultItem:hover { + border-color: var(--border-strong); } -.resultItem { - width: 100%; - padding: 10px 12px; - background: var(--surface-2); - border-radius: var(--radius-sm); - margin-bottom: 6px; - font-size: 13px; - color: var(--text-primary); - line-height: 1.6; - border: 0.5px solid transparent; +.resultItemBody { + flex: 1; + min-width: 0; +} + +.deleteBtn { + flex-shrink: 0; + background: transparent; + border: none; cursor: pointer; - transition: border .14s, background .14s, transform .12s; - position: relative; - animation: fadeUp .3s ease both; - display: block; + color: var(--text-tertiary); + font-size: 13px; + line-height: 1; + padding: 3px; + border-radius: 4px; + opacity: 0; + transition: + opacity 0.15s, + color 0.15s; +} + +.resultItem:hover .deleteBtn, +.deleteBtn:focus-visible { + opacity: 1; +} + +.deleteBtn:hover { + color: var(--danger); } .generationResult { @@ -302,15 +257,19 @@ } .generationTitle { + font-family: var(--font-mono); + font-size: 10px; font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; margin-bottom: 8px; color: var(--accent); } .generationContent :global(ul), .generationContent :global(ol) { - padding-left: 1.25rem; - margin: 0.5rem 0; + padding-left: 1.1rem; + margin: 0.4rem 0; } .generationContent :global(ul) { @@ -322,7 +281,11 @@ } .generationContent :global(li) { - margin: 0.2rem 0; + margin: 0.3rem 0; +} + +.generationContent :global(li)::marker { + color: var(--text-tertiary); } .generationContent :global(p) { @@ -333,59 +296,25 @@ margin-top: 0.65rem; } -.resultItem:nth-child(1) { - animation-delay: 0s; -} - -.resultItem:nth-child(2) { - animation-delay: .06s; -} - -.resultItem:nth-child(3) { - animation-delay: .12s; -} - -.resultItem:nth-child(4) { - animation-delay: .18s; -} - -.resultItem:nth-child(5) { - animation-delay: .24s; -} - -.resultItem:nth-child(n+6) { - animation-delay: .3s; -} - -.resultItem:hover { - border-color: var(--border); - background: #ECEAE4; - transform: translateX(2px); -} - -.resultItem:last-child { - margin-bottom: 0; -} - -.loadingBar { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--accent); - border-radius: 0 2px 2px 0; - width: 0%; - transition: width .05s linear; +/* Skeleton Loading */ +.skeletonContainer { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; } -/* Skeleton Loading */ .skeleton { - background: linear-gradient(90deg, var(--surface-2) 25%, #E8E6E0 50%, var(--surface-2) 75%); + background: linear-gradient( + 90deg, + var(--surface-2) 25%, + var(--border) 50%, + var(--surface-2) 75% + ); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: 4px; height: 13px; - margin-bottom: 8px; width: 100%; } @@ -397,16 +326,25 @@ width: 70%; } +/* Disclaimer */ +.disclaimer { + flex-shrink: 0; + font-family: var(--font-display); + font-style: italic; + font-size: 11.5px; + color: var(--text-tertiary); + text-align: center; + line-height: 1.5; + padding: 0 8px; +} + /* Animations */ @keyframes shimmer { 0% { background-position: 200% 0; } - 50% { - background-position: -200% 0; - } 100% { - background-position: 200% 0; + background-position: -200% 0; } } @@ -429,261 +367,3 @@ opacity: 1; } } - -.noteTextWrapper { - margin-right: 2px; - margin-left: 2px; -} - -/* Results States */ -.errorMessage { - color: var(--text-tertiary); - font-size: 13px; - margin-bottom: 8px; -} - -.emptyStateContainer { - text-align: center; -} - -.skeletonContainer { - width: 100%; - display: flex; - flex-direction: column; - gap: 8px; -} - -/* Disclaimer */ -.disclaimer { - font-size: 11px; - color: var(--text-tertiary); - text-align: center; - margin-top: 16px; - line-height: 1.5; - padding: 0 8px; - animation: fadeIn .4s ease both .3s; -} - -@keyframes shimmer-bar { - 0% { - width: 10%; - } - 50% { - width: 60%; - } - 100% { - width: 10%; - } -} - -.historyContainer { - justify-content: center; - align-items: center; - height: 100%; - overflow-y: scroll; -} - -.historyButtonWrapper { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - margin-top: 8px; - position: relative; -} - -.historyButtonWrapper { - opacity: 1; - visibility: visible; -} - -.savedPageTooltip { - position: absolute; - top: -85%; - left: 50%; - transform: translateX(-50%); - background-color: rgba(237, 237, 237, 0.7) !important; - color: rgba(90, 90, 90, 0.7); - padding: 4px 8px; - border-radius: 2px 0px 2px 2px; - font-size: 0.8rem; - font-weight: 300; - white-space: nowrap; - z-index: 1000; - opacity: 0; - visibility: hidden; - transition: - opacity 0.2s ease-in-out, - visibility 0.2s ease-in-out; - pointer-events: none; - box-shadow: -1px 2px 3px 0 rgba(120, 60, 20, 0.1); -} - -.historyButton:hover .savedPageTooltip { - opacity: 1; - visibility: visible; -} - -.historyButton { - background-color: #ffffff; - font-size: 0.8rem; - font-weight: 600; - padding: 8px 12px; - border: 1px solid #f0f0f0; - transition: 0.15s; - cursor: pointer; - border-radius: 24px; - display: flex; - position: relative; -} - -.historyButton:hover { - background-color: #f0f0f0; - border: 1px solid #f0f0f0; - box-shadow: -1px 3px 5px 0 rgba(120, 60, 20, 0.1); - transition: 0.15s; -} - -.historyItemContainer { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin-top: 4px; - transition: 0.15s; -} - -.savedPageIconIndicatorContainer { - justify-items: center; - align-items: center; - transition: 0.15s; -} - -.savedPageIconIndicator { - color: #bbbbbb; - font-size: 0.7rem; - justify-items: center; - padding-bottom: 3px; -} - -.savedPageIcon { - color: #bbbbbb; - font-size: 1.2rem; - padding-top: 1px; - transition: 0.15s; -} - -.savedPageIconActive { - color: gold; - font-size: 1.2rem; - padding-top: 1px; - transition: 0.15s; -} - -.historyItem { - display: flex; - padding: 4px; - padding: 16px; - background-color: #f8f8f8; - border-radius: 16px; - transition: 0.15s; - margin: 8px 2px; -} - -.historyText { - flex: 1; -} - -.savedIconsContainer { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; -} - -.genTypeIconWrapper { - align-items: center; - align-content: center; - justify-content: center; - margin-left: auto; - margin-top: auto; - border-radius: 12px; - transition: 0.15s; - background-color: #ffffff; -} - -.genTypeIconWrapper_obscured { - align-content: center; - text-align: center; - margin-left: auto; - margin-top: auto; - border-radius: 12px; - transition: 0.15s; - background-color: #ffffff; - padding-left: 6px; - padding-right: 6px; -} - -.savedTypeIcon { - border-radius: 12px; - padding: 4px; - display: flex; - color: #888888; - justify-content: right; - align-items: center; - text-align: center; - transition: 0.5s; -} - -.historyEmptyWrapper { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin-top: 16px; - color: #888888; - font-size: 0.8rem; - font-weight: 300; - transition: 0.15s; -} - -.historyDoc { - cursor: pointer; - transition: 0.15s; - color: #aaaaaa; - margin-bottom: 4px; - font-size: 0.8rem; -} - -.historyDoc:hover { - color: #9c9ae1; -} - -/* Transition animations for saved items */ -:global(.saved-item-enter) { - opacity: 0; - max-height: 0; - overflow: hidden; - transform: translateY(-20px); -} - -:global(.saved-item-enter-active) { - opacity: 1; - max-height: 1000px; - transform: translateY(0); - transition: opacity 300ms ease-in-out, max-height 300ms ease-in-out, transform 300ms ease-in-out; -} - -:global(.saved-item-exit) { - opacity: 1; - max-height: 1000px; - overflow: hidden; - transform: translateY(0); -} - -:global(.saved-item-exit-active) { - opacity: 0; - max-height: 0; - transform: translateY(-20px); - transition: opacity 300ms ease-in-out, max-height 300ms ease-in-out, transform 300ms ease-in-out; -} diff --git a/frontend/src/pages/revise/index.tsx b/frontend/src/pages/revise/index.tsx index dc1fc3a4..7b88fff6 100644 --- a/frontend/src/pages/revise/index.tsx +++ b/frontend/src/pages/revise/index.tsx @@ -361,7 +361,7 @@ ${request} docContext.afterCursor.length === 0 ) { return ( -
+
The document seems to be empty. Either you haven't written anything yet, or the text is still loading.
diff --git a/frontend/src/pages/revise/styles.module.css b/frontend/src/pages/revise/styles.module.css index 7c5ddecb..f672697f 100644 --- a/frontend/src/pages/revise/styles.module.css +++ b/frontend/src/pages/revise/styles.module.css @@ -1,96 +1,36 @@ -:root { - --bg: #f0ede6; - --surface: #ffffff; - --surface-2: #fafaf8; - --border: rgba(0,0,0,0.08); - --text-primary: #1c1c1c; - --text-secondary: #6b6b6b; - --text-tertiary: #b3aba0; - --accent: #2d5be3; - --accent-bg: #f7f8ff; - --accent-border: #c4caff; - --radius: 10px; - --radius-sm: 8px; -} +/* Revise page — design tokens come from src/theme.css */ .app { width: 100%; - max-width: 480px; - background: var(--surface); - overflow: hidden; - box-shadow: 0 4px 24px rgba(0,0,0,0.07); - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 0.5rem; - position: relative; - padding: 0.5rem 0.1rem; height: 100%; -} - -.tabs { - display: flex; - padding: 12px 12px 0; - background: var(--surface); - border-bottom: 1px solid var(--border); - gap: 4px; -} - -.tabBtn { - flex: 1; display: flex; flex-direction: column; - align-items: center; - gap: 2px; - padding: 8px 10px 10px; - border: 1px solid transparent; - border-bottom: none; - border-radius: 6px 6px 0 0; - background: transparent; - font-size: 13px; - font-weight: 500; - color: var(--text-tertiary); - cursor: pointer; - font-family: 'DM Sans', sans-serif; - transition: all 0.15s; - margin-bottom: -1px; -} - -.tabBtn:hover { color: var(--surface-2); } - -.tabBtn.active { - border-color: var(--border); - border-bottom-color: var(--surface-2); - background: var(--surface-2); + background: var(--bg); color: var(--text-primary); -} - -.tabHint { - font-size: 10px; - font-weight: 400; - color: inherit; - opacity: 0.7; + font-family: var(--font-ui); + position: relative; } .body { - padding: 16px; + padding: 14px; overflow-y: auto; flex: 1; min-height: 0; - padding-bottom: 24px; + scrollbar-width: thin; + scrollbar-color: var(--border-strong) transparent; } .sectionLabel { display: flex; align-items: center; gap: 10px; + font-family: var(--font-mono); font-size: 10px; - font-weight: 600; + font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); margin: 0 0 12px; - font-family: 'DM Mono', monospace; } .sectionLabel::after { @@ -101,8 +41,8 @@ } .sectionNumber { - background: var(--text-primary); - color: var(--surface-2); + background: var(--accent); + color: var(--surface); width: 18px; height: 18px; border-radius: 50%; @@ -114,21 +54,29 @@ flex-shrink: 0; } -.todoSection { margin-bottom: 24px; } +.todoSection { + margin-bottom: 24px; +} .block { background: var(--surface); - border: 1.5px solid var(--border); + border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 10px; overflow: hidden; - transition: border-color 0.15s; + transition: border-color 0.15s, box-shadow 0.15s; animation: fadeUp 0.2s ease both; + box-shadow: var(--shadow-sm); } -.block:focus-within { border-color: var(--accent); } +.block:focus-within { + border-color: var(--accent-border); + box-shadow: 0 0 0 2px var(--accent-bg); +} -.blockHead { padding: 12px 14px 0; } +.blockHead { + padding: 12px 14px 0; +} .blockTitle { font-size: 13px; @@ -150,26 +98,32 @@ textarea { background: transparent; font-size: 13px; color: var(--text-primary); - font-family: 'DM Sans', sans-serif; + font-family: var(--font-ui); resize: none; line-height: 1.6; padding: 8px 14px 14px; } -textarea::placeholder { color: #c5bfb8; } +textarea::placeholder { + color: var(--text-tertiary); +} -.featuresSection { margin-bottom: 16px; } +.featuresSection { + margin-bottom: 16px; +} -.featGroup { margin-bottom: 18px; } +.featGroup { + margin-bottom: 18px; +} .featGroupLabel { + font-family: var(--font-mono); font-size: 10px; - font-weight: 600; + font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-tertiary); margin-bottom: 8px; - font-family: 'DM Mono', monospace; } .featGrid { @@ -183,33 +137,46 @@ textarea::placeholder { color: #c5bfb8; } align-items: center; gap: 8px; padding: 9px 12px; - border: 1.5px solid var(--border); + border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); cursor: pointer; text-align: left; - transition: all 0.14s ease; - font-family: 'DM Sans', sans-serif; + transition: + border-color 0.14s ease, + background 0.14s ease; + font-family: var(--font-ui); + box-shadow: var(--shadow-sm); } .featBtn:hover { - border-color: var(--accent); + border-color: var(--accent-border); background: var(--accent-bg); } +.featBtn:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--accent-border); +} + .featBtn.on { - border-color: #2d9e6b; - background: #edf7f2; + border-color: var(--positive-border); + background: var(--positive-bg); } -.featBtn.on .featLabel { color: #1b7a4e; } -.featBtn.on .featDot { background: #1b7a4e; } +.featBtn.on .featLabel { + color: var(--positive); +} + +.featBtn.on .featDot { + background: var(--positive); +} .featDot { width: 6px; height: 6px; border-radius: 50%; - background: #d0ccc6; + background: var(--border-strong); flex-shrink: 0; transition: background 0.14s; } @@ -225,7 +192,7 @@ textarea::placeholder { color: #c5bfb8; } .footer { position: sticky; bottom: 0; - padding: 12px 16px 16px; + padding: 12px 14px 14px; background: var(--surface); border-top: 1px solid var(--border); flex-shrink: 0; @@ -247,9 +214,9 @@ textarea::placeholder { color: #c5bfb8; } .selectedTag { display: inline-flex; align-items: center; - background: #edf7f2; - color: #1b7a4e; - border: 1px solid #b3dec7; + background: var(--positive-bg); + color: var(--positive); + border: 1px solid var(--positive-border); border-radius: 20px; font-size: 11px; font-weight: 500; @@ -261,38 +228,55 @@ textarea::placeholder { color: #c5bfb8; } padding: 11px; border-radius: var(--radius-sm); border: none; - background: var(--text-primary); - color: var(--surface-2); + background: var(--accent); + color: #ffffff; font-size: 14px; font-weight: 600; cursor: pointer; - font-family: 'DM Sans', sans-serif; - transition: opacity 0.15s, transform 0.1s; + font-family: var(--font-ui); + transition: + background 0.15s, + transform 0.1s; } -.runBtn:hover { opacity: 0.88; } -.runBtn:active { transform: scale(0.99); } +.runBtn:hover { + background: var(--accent-strong); +} + +.runBtn:active { + transform: scale(0.99); +} + +.runBtn:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent-border); +} .runBtn:disabled { - background: var(--border); + background: var(--surface-2); color: var(--text-tertiary); cursor: default; - opacity: 1; } -.runBtn:disabled:hover { opacity: 1; transform: none; } +.runBtn:disabled:hover { + background: var(--surface-2); + transform: none; +} .resultPanel { margin-top: 12px; background: var(--surface); - border: 1.5px solid var(--border); + border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; animation: fadeUp 0.25s ease both; display: none; + box-shadow: var(--shadow-sm); } -.resultPanel.visible { display: block; } +.resultPanel.visible { + display: block; +} .resultHeader { display: flex; @@ -313,10 +297,10 @@ textarea::placeholder { color: #c5bfb8; } } .resultMeta { + font-family: var(--font-mono); font-size: 10px; color: var(--text-tertiary); margin-left: auto; - font-family: 'DM Mono', monospace; } .resultItem { @@ -324,17 +308,43 @@ textarea::placeholder { color: #c5bfb8; } font-size: 13px; color: var(--text-primary); line-height: 1.65; - border-bottom: 1px solid var(--bg); - cursor: pointer; + border-bottom: 1px solid var(--surface-2); transition: background 0.12s; animation: fadeUp 0.2s ease both; } -.resultItem:last-child { border-bottom: none; } -.resultItem:hover { background: var(--surface-2); } -.resultItem:nth-child(2) { animation-delay: 0.04s; } -.resultItem:nth-child(3) { animation-delay: 0.08s; } -.resultItem:nth-child(4) { animation-delay: 0.12s; } +.resultItem:last-child { + border-bottom: none; +} + +.resultItem :global(ul), +.resultItem :global(ol) { + padding-left: 1.1rem; + margin: 0.4rem 0; +} + +.resultItem :global(ul) { + list-style: disc; +} + +.resultItem :global(ol) { + list-style: decimal; +} + +.resultItem :global(li)::marker { + color: var(--text-tertiary); +} + +.resultItem :global(a) { + color: var(--accent); + text-decoration: underline; + text-decoration-color: var(--accent-border); + text-underline-offset: 2px; +} + +.resultItem :global(a:hover) { + color: var(--accent-strong); +} .loadingState { display: flex; @@ -347,7 +357,10 @@ textarea::placeholder { color: #c5bfb8; } font-size: 13px; } -.loaderDots { display: flex; gap: 5px; } +.loaderDots { + display: flex; + gap: 5px; +} .loaderDots span { width: 6px; @@ -357,15 +370,34 @@ textarea::placeholder { color: #c5bfb8; } animation: bounce 1.2s infinite ease-in-out; } -.loaderDots span:nth-child(2) { animation-delay: 0.2s; } -.loaderDots span:nth-child(3) { animation-delay: 0.4s; } +.loaderDots span:nth-child(2) { + animation-delay: 0.2s; +} + +.loaderDots span:nth-child(3) { + animation-delay: 0.4s; +} @keyframes bounce { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1); opacity: 1; } + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } } @keyframes fadeUp { - from { opacity: 0; transform: translateY(5px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(5px); + } + to { + opacity: 1; + transform: translateY(0); + } } diff --git a/frontend/src/taskpane.css b/frontend/src/taskpane.css index fbf91e54..a6f49e90 100644 --- a/frontend/src/taskpane.css +++ b/frontend/src/taskpane.css @@ -8,7 +8,8 @@ height: 100%; margin: 0; padding: 0; - background: white; + /* Warm paper — see src/theme.css for the token definitions */ + background: var(--bg, #f7f4ed); } } diff --git a/frontend/src/theme.css b/frontend/src/theme.css new file mode 100644 index 00000000..e59ec711 --- /dev/null +++ b/frontend/src/theme.css @@ -0,0 +1,47 @@ +/* + * Thoughtful design system — single source of truth for the sidebar UI. + * + * Brand: "AI that helps you think" (thoughtful-ai.com). Warm paper surfaces, + * ink text, a fountain-pen indigo accent, and a serif display face for the + * wordmark and key moments. Every page module consumes these tokens; do not + * redeclare them in component CSS. + */ +:root { + /* Paper & ink */ + --bg: #f7f4ed; + --surface: #ffffff; + --surface-2: #f1ede4; + --border: #e7e2d6; + --border-strong: #d7d1c2; + --text-primary: #232019; + --text-secondary: #6e6a5f; + --text-tertiary: #a39e90; + + /* Accent — fountain-pen indigo */ + --accent: #4038b8; + --accent-strong: #322b96; + --accent-bg: #efeefb; + --accent-border: #c9c6ef; + + /* Affirmative — selected/enabled states */ + --positive: #20724f; + --positive-bg: #ecf6f0; + --positive-border: #bcdfcc; + + --danger: #b4534b; + + --radius: 10px; + --radius-sm: 7px; + + --font-ui: 'DM Sans Variable', 'DM Sans', 'Segoe UI', system-ui, sans-serif; + --font-display: 'Source Serif 4 Variable', 'Source Serif 4', Georgia, serif; + --font-mono: 'DM Mono', ui-monospace, 'Cascadia Mono', monospace; + + --shadow-sm: 0 1px 2px rgba(35, 32, 25, 0.06); + --shadow-md: 0 2px 10px rgba(35, 32, 25, 0.08); +} + +::selection { + background: var(--accent-bg); + color: var(--accent-strong); +} diff --git a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-chromium-linux.png b/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-chromium-linux.png index 419ad1ff..bdd4ab5b 100644 Binary files a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-chromium-linux.png and b/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-chromium-linux.png differ diff --git a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-firefox-linux.png b/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-firefox-linux.png deleted file mode 100644 index 3d4692d9..00000000 Binary files a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-firefox-linux.png and /dev/null differ diff --git a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-webkit-linux.png b/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-webkit-linux.png deleted file mode 100644 index 3d4692d9..00000000 Binary files a/frontend/tests/demo-page-visual.spec.ts-snapshots/demo-page-webkit-linux.png and /dev/null differ