Files
SealShare/docs/plans/screenshots-and-website.md
T
Andreas Reinhold / reiniandClaude Opus 5 606cc766f2
docker / test (8.5) (push) Successful in 2m5s
docker / build-and-push (push) Failing after 9m38s
docker / release (push) Has been skipped
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m4s
Add the SealShare website for sealshare.nonameweb.ch
website/ is hand-written HTML and CSS, uploaded as it is, like the
MailifySMS site: SealShare as software a company installs for its own
upload platform, how it works, features, a desktop/phone and light/dark
gallery of the generated screenshots, a dated and sourced comparison with
hosted transfer services and self-hosted tools, the Docker quick start,
FAQ and a privacy page for Plausible. Colours come from the app's scheme,
Google Sans Flex is served locally, nothing else loads from other hosts.

The README shows three screenshots and no longer calls the encryption
end-to-end. WebsiteTest guards missing files, other hosts, the screenshot
set and the encryption wording.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 12:38:49 +02:00

19 KiB
Raw Blame History

Screenshots and the SealShare website

Goal

Two things that feed each other. First, one command — composer screenshots — produces every screenshot of SealShare from fixed demo data, desktop and phone, light and dark, ready for the web. Second, a static website at sealshare.nonameweb.ch, made the way mailifysms.nonameweb.ch is: hand-written HTML and CSS in website/, uploaded by hand. The site presents SealShare as what it is — software a company installs to run its own upload platform, so it exchanges files with customers securely without relying on an outside service — shows the screenshots, compares SealShare with hosted transfer services and with other self-hosted tools, and tells how to install it. It goes live with 2.0.0. The README gets a few of the same screenshots, and its encryption wording is corrected.

Context

MailifySMS, the model (../MailifySMS):

  • website/ holds index.html, privacy_policy.html, terms_and_conditions.html, css/theme.css (a palette sampled from the app's screenshots), css/device-frame.css (a phone bezel shared with the store canvases), self-hosted Poppins (fonts/, OFL) and img/ (icon.png, hero.jpg, screenshots/{light,dark}/NN-name.png at 540px).
  • index.html: sticky nav with a phone toggle, hero, "How it works", "Key features", a screenshot gallery with a Light/Dark switch (data-light/data-dark on each <img>), FAQ accordion, contact card (surtic86@gmail.com), footer (quick links, legal). Plausible: <script defer data-domain="mailifysms.nonameweb.ch" src="https://plausible.io/js/script.js">. The page's JS is one inline <script> at the end.
  • CLAUDE.md records that website/ "is a faithful copy of what is deployed, images included, so it can be uploaded wholesale". There is no deploy automation.
  • tools/screenshots.sh (macOS only) drives an emulator and headless Chrome; documented in CLAUDE.md § Screenshots, with the reasons behind each quirk.

Hosting. *.nonameweb.ch is a wildcard DNS record to 80.74.140.2 (METANET shared hosting, nginx), the same as mailifysms. sealshare.nonameweb.ch resolves already; HTTP serves the host's placeholder, HTTPS has no certificate. Creating the site and its Let's Encrypt certificate is done in the hosting panel.

SealShare.

  • Laravel 13.31, Livewire 4.4, Livewire Material 1.0.1, Pest 5.1 with pestphp/pest-plugin-browser (Playwright 1.63). The browser tests run the app in-process, so factories, Storage::fake() and travelTo() shape what the browser sees. tests/Pest.php applies Tests\TestCase and RefreshDatabase to Feature and Browser, and creates an admin in beforeEach (the setup gate).
  • Device presets: visit()->on()->macbook14() is 1512×982 at 2× (a 3024×1964 capture); on()->iPhone15Pro() is 393×852 at 3× (1179×2556). inLightMode() / inDarkMode(), screenshot(fullPage, filename). Screenshots are written to tests/Browser/Screenshots/<name>.png; the directory is created, subdirectories in the name are not — names must be flat. Pest empties that directory when a browser run starts, and has no reduced-motion emulation.
  • Pest's in-process server does not store a multipart upload, so a browser test cannot select files through the file input.
  • PHP here has GD with WebP and PNG support; the production image does not need it (this is a development tool).
  • Colours: resources/css/material-scheme.json (seed #4f46e5, Vibrant) holds the light and dark roles as hexes. Font: Google Sans Flex, vendor/nonameweb/livewire-material/resources/fonts/google-sans-flex/GoogleSansFlex-Latin.woff2 with its OFL.txt. Logo: resources/views/components/app-logo-icon.blade.php (SVG).
  • Encryption, as the code does it: ShareService::createShare() encrypts each uploaded file on the server with AES-256-GCM (chunked) through FileEncryptionService. Without a share password the key is stored in shares.encryption_key; with one, the key is derived with PBKDF2-SHA256 and never stored. The server sees the plaintext while uploading and downloading. The README calls this "End-to-End Encryption", which it is not.
  • The upload page is public, optionally behind the system password (SystemPasswordGate); a customer given that password can upload and send the link back.
  • Install today (README): ghcr.io/surtic86/sealshare, clone from GitHub. Gitea (gitea.nonameweb.ch/noNameWEB/SealShare) is now the public repository; the image registry for 2.0.0 is settled separately.
  • .gitignore does not ignore tests/Browser/Screenshots; .dockerignore excludes tests and *.md but would copy a website/ directory into the image.

Peers. Pingvin Share has been archived since June 2025 (its README points to forks such as Pingvin Share X). PsiTransfer, Gokapi and Erugo are single-purpose self-hosted share tools; Gokapi advertises end-to-end encryption.

Decisions

  • Positioning: software you host, not a service — the site says plainly that SealShare is not hosted by anyone but the company that installs it: its own upload platform for exchanging files with customers, data on its own server, no dependence on an external service.
  • Pages: index.html and privacy.html — one landing page, and a short privacy page because the site uses Plausible. No terms page: the software is MIT-licensed and no service is offered.
  • A comparison with hosted transfer services and with self-hosted share tools — two tables on the landing page. Cloud suites (Nextcloud-style) are left out.
  • Hand-written HTML and CSS, like MailifySMS — no build step; website/ is uploaded as it is.
  • Colours copied from material-scheme.json, not sampledwebsite/css/theme.css lists the roles it uses with the scheme's hexes, light by default and dark under @media (prefers-color-scheme: dark); it names the seed and variant it was copied from, so a regenerated scheme is copied again. The site follows the visitor's system theme and has no toggle of its own.
  • Google Sans Flex, self-hosted — the app's font, copied with its OFL.txt into website/fonts/; nothing is loaded from Google.
  • Uploaded by hand, like MailifySMSwebsite/ is a faithful copy of what is live. You create the subdomain and certificate once in the hosting panel and upload the folder when it changes. No hosting credentials anywhere in the repository or CI.
  • Plausibledata-domain="sealshare.nonameweb.ch", the same script as MailifySMS; the site must be added in the Plausible account.
  • English only.
  • Screenshots: desktop and phone, each in light and dark (20 images) — desktop (MacBook 14, 2×): 01-upload (files selected, options filled), 02-share-created, 03-qr-code (the dialog), 04-download (the recipient's file list), 05-admin-dashboard, 06-admin-settings; phone (iPhone 15 Pro, 3×): 01-upload, 02-password (the recipient's password prompt), 03-download, 04-qr-code.
  • Screenshots run as Pest browser tests in tests/Screenshots/, started by composer screenshots — the directory is not one of phpunit.xml's test suites, so php artisan test, the Browser suite and CI never run it. It reuses Playwright and the in-process server.
  • Fixed demo data — factories and ShareService with fixed names, sizes and tokens, time frozen with travelTo(), the site title and branding at their defaults, one admin ("Alex Morgan"). Every run produces the same images unless the UI changed.
  • Images published as WebP by the test run itself — after each capture a small helper resizes it with GD into website/img/screenshots/{desktop,phone}/{light,dark}/NN-name-<width>.webp at two widths (desktop 1600 and 800 px, phone 1080 and 540 px) for srcset. Raw PNGs stay in tests/Browser/Screenshots/, which is gitignored; only the WebP files are committed.
  • Device frames in CSSwebsite/css/device-frame.css draws a laptop and a phone around the screenshots; the hero shows the desktop upload and the phone download screenshots framed, in the visitor's theme. No generated hero image.
  • README shows three screenshots — desktop upload, desktop share created, phone download (light), referenced from website/img/screenshots/…, so each image exists once in the repository.
  • Encryption is described accurately, on the site and in the README — "encrypted at rest with AES-256-GCM; with a share password the key is never stored". The comparison marks end-to-end encryption "no" for SealShare. The README's "End-to-End Encryption" line is corrected.
  • Built on material, live with 2.0.0 — screenshots show the 2.0.0 interface; the site links the Gitea repository, and its install commands are the README's at release, whatever registry 2.0.0 ships with.
  • Comparison facts are researched, dated and sourced — from each product's own site, documentation or repository; the tables say "as of " and link every source. Hosted services: WeTransfer, SwissTransfer, Dropbox Transfer, Google Drive links. Self-hosted tools: open source, single-purpose, installable with Docker, with a release in the 12 months before the research — expected Pingvin Share X, PsiTransfer, Gokapi and Erugo; any that fails the rule is dropped and named in the commit message. Criteria (rows): where files are stored, who operates it, recipient needs an account, password protection, expiry, download limit, encryption at rest, end-to-end encryption, folder upload, custom branding, maximum file size, licence and cost. A value that cannot be sourced is "—", never guessed.

Out of scope

  • A terms page, a documentation section, a German version.
  • Deploy automation (Gitea Actions, SFTP scripts), and creating the subdomain, certificate or Plausible site — those are yours in the hosting panel and the Plausible account.
  • Comparing with Nextcloud, ownCloud or other cloud suites.
  • Store-style canvases with captions, a generated hero image, video or animated screenshots.
  • Running the screenshots in CI, or checking them against earlier runs (visual regression).
  • The image registry move and the install commands' final form (settled with 2.0.0).
  • Implementing end-to-end encryption.

Implementation steps

  1. Housekeeping. .gitignore: /tests/Browser/Screenshots. .dockerignore: website.
  2. Screenshot helper. tests/Screenshots/Publisher.php (Tests\Screenshots\Publisher): publish(string $capture, string $device, string $theme, string $name, array $widths): void reads tests/Browser/Screenshots/<capture>.png with GD, and for each width writes website/img/screenshots/<device>/<theme>/<name>-<width>.webp (quality 82, aspect kept, directories created). It throws when the capture is missing, so a failed shot fails the run.
  3. Demo data. tests/Screenshots/DemoData.php: admin(), shares() (eight shares with fixed tokens, file names such as Q3-report.pdf, Contract 2026.pdf, Product photos/…, sizes, download counts and expiries, one password-protected, one expired), created through factories and ShareService with Storage::fake('shares') so the files exist encrypted.
  4. Screenshot tests. tests/Screenshots/ScreenshotsTest.php, with tests/Pest.php extended to ->in('Feature', 'Browser', 'Screenshots'):
    • beforeEach: config(['session.driver' => 'file']), travelTo('2026-10-01 09:30'), demo data; a ready() wait as in SealShareTest.
    • One test per device and theme (four tests), each visiting the pages in turn, waiting for networkidle and fonts (document.fonts.ready), hiding the text caret, capturing viewport-sized (not full-page) shots, and calling Publisher::publish() right after each.
    • The upload shot with files selected: create Livewire temporary uploads on the fake livewire-tmp disk and set the uploader's property through $wire.$set with livewire-file: references, then fill the options. If Livewire refuses that, the shot shows the drop zone with the options filled instead, and the plan's risk note is updated.
    • QR dialog: click('[data-test="show-qr-code"]'); password prompt: the protected share on the phone; admin pages as the admin.
  5. Command. composer.json script "screenshots": Composer\\Config::disableProcessTimeout, npm run build, @php vendor/bin/pest tests/Screenshots — the build first, so the shots show the current assets. Playwright's Chromium must be installed (npx playwright install chromium), as for the browser tests.
  6. Website scaffold. website/:
    • css/theme.css — tokens copied from material-scheme.json (with seed and variant noted), Google Sans Flex @font-face, layout, nav, hero, sections, cards, tables (scrolling sideways on a phone), FAQ (<details>), footer; light and dark through prefers-color-scheme.
    • css/device-frame.css — laptop and phone frames.
    • fonts/GoogleSansFlex-Latin.woff2, fonts/OFL.txt; img/logo.svg (from app-logo-icon), img/icon.png (favicon, from public/).
  7. Landing page. website/index.html (Plausible in <head>, one inline script at the end):
    • nav: Why, Features, Screenshots, Compare, Install, FAQ, Gitea;
    • hero: "Your own secure upload platform" — self-hosted file exchange with customers, no outside service; buttons "Install" (to #install) and "Source on Gitea"; framed desktop and phone screenshots as <picture> elements whose <source media="(prefers-color-scheme: dark)"> picks the dark captures;
    • "Why run your own": your server, your domain and branding, customers upload and download without accounts, encrypted at rest, no per-seat pricing;
    • "How it works": upload → link or QR code → the customer downloads, with expiry, download limit and password;
    • features (from the README, accurate encryption wording);
    • screenshots: Desktop/Phone and Light/Dark switches over one gallery (Light/Dark starting on the visitor's system theme), srcset for both widths, loading="lazy", descriptive alt;
    • compare: the two dated tables with sources (step 8);
    • install: the README's Docker quick start and a link to the full instructions on Gitea;
    • FAQ: "Is it end-to-end encrypted?" (no — at rest, and what a password adds), "Can customers send files to us?" (yes, through the upload page, optionally behind the system password), "How big can files be?" (the README's large-file limits), "What does it cost?" (MIT, your hosting), "Who runs it?" (you);
    • contact (surtic86@gmail.com, as MailifySMS) and footer (Gitea, licence, privacy, noNameWEB).
  8. Comparison research. For each product, record every criterion with its source URL and the date checked; apply the self-hosted selection rule; fill the tables. Keep the notes in the commit message, not in the repository.
  9. Privacy page. website/privacy.html: who runs the site (contact), the host (METANET, server logs), Plausible (cookieless, no personal data, EU-hosted, link to its data policy), no other third parties, fonts served locally, contact for questions; dated.
  10. README and changelog. Correct the encryption lines (the intro sentence stays accurate; "End-to-End Encryption" becomes "Encryption at Rest", described as in Decisions), add a Screenshots section with the three images, add the website link. CHANGELOG 2.0.0 "Fixed": the README no longer calls the encryption end-to-end. The website and the screenshot tooling get no changelog entry — they do not change the application.
  11. Project notes. record-rule for website/**: website/ is a faithful copy of what is live, uploaded by hand; its colours are copied from material-scheme.json and must be copied again when the scheme is regenerated; the comparison is dated and every value sourced. And for tests/Screenshots/**: run with composer screenshots whenever the interface changes, before a release; the demo data is fixed so runs are reproducible.

Testing

  • tests/Unit/ScreenshotPublisherTest.php: a generated PNG is written as WebP at each requested width with the aspect ratio kept, into the device/theme directory; a missing capture throws.
  • tests/Feature/WebsiteTest.php guards website/ without a browser:
    • every local src, href, srcset entry and CSS url() resolves to a file in website/;
    • every screenshot the gallery or README references exists for both widths and both themes;
    • no request goes to a host other than plausible.io (no Google Fonts, no CDN);
    • index.html and privacy.html have a <title>, lang="en" and a meta description;
    • the README has no "End-to-End Encryption" line, the site's features section does not say "end-to-end", and SealShare's end-to-end cell in the comparison (marked data-compare="sealshare-e2e") reads "No" — the FAQ may still ask the question.
  • The screenshot run itself is the test of step 4: it fails when a page, selector or capture breaks. It is run by hand before a release, not in CI.
  • The site is looked at in Chrome, Firefox and Safari, light and dark, at phone width, before uploading.

Risks and open questions

  • Selecting files in the upload shot works: the files are stored with Livewire's own FileUploadConfiguration::storeTemporaryFile() and handed to _finishUpload by their signed names; Livewire's temporary-upload cleanup is turned off for the run, because under the frozen clock it deletes them.
  • Comparison accuracy and fairness. Other products change; the tables are dated and sourced, and re-checked when the site is updated. Swiss unfair-competition law expects comparisons to be accurate and not misleading — values that cannot be sourced stay "—".
  • Install commands depend on the registry move. Until it is settled, the install section copies the current README; it is updated before the site goes live with 2.0.0.
  • Screenshot determinism. Relative dates ("in 3 days") depend on travelTo(); animations (the share-created shape, counting stats, dialog entry) are waited out — Pest has no reduced-motion emulation — by waiting on document.getAnimations().length === 0 before each capture.
  • The host's name in the privacy page (METANET) is inferred from the server's reverse DNS (urbanus.ch-meta.net); confirm it before the page goes live.
  • The contact address is the one MailifySMS publishes (surtic86@gmail.com); change it in step 7 if SealShare should have its own.
  • Image weight. Twenty screenshots at two widths as WebP should stay under ~4 MB in total; if not, lower the quality or drop the larger phone width.
  • Colours drift when the scheme is regenerated; the rule in step 11 and the note in theme.css are the guard.