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
19 KiB
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/holdsindex.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) andimg/(icon.png,hero.jpg,screenshots/{light,dark}/NN-name.pngat 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-darkon 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.mdrecords thatwebsite/"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 inCLAUDE.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()andtravelTo()shape what the browser sees.tests/Pest.phpappliesTests\TestCaseandRefreshDatabasetoFeatureandBrowser, and creates an admin inbeforeEach(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 totests/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.woff2with itsOFL.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) throughFileEncryptionService. Without a share password the key is stored inshares.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. .gitignoredoes not ignoretests/Browser/Screenshots;.dockerignoreexcludestestsand*.mdbut would copy awebsite/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.htmlandprivacy.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 sampled —website/css/theme.csslists 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.txtintowebsite/fonts/; nothing is loaded from Google. - Uploaded by hand, like MailifySMS —
website/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. - Plausible —
data-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 bycomposer screenshots— the directory is not one of phpunit.xml's test suites, sophp artisan test, the Browser suite and CI never run it. It reuses Playwright and the in-process server. - Fixed demo data — factories and
ShareServicewith fixed names, sizes and tokens, time frozen withtravelTo(), 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>.webpat two widths (desktop 1600 and 800 px, phone 1080 and 540 px) forsrcset. Raw PNGs stay intests/Browser/Screenshots/, which is gitignored; only the WebP files are committed. - Device frames in CSS —
website/css/device-frame.cssdraws 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
- Housekeeping.
.gitignore:/tests/Browser/Screenshots..dockerignore:website. - Screenshot helper.
tests/Screenshots/Publisher.php(Tests\Screenshots\Publisher):publish(string $capture, string $device, string $theme, string $name, array $widths): voidreadstests/Browser/Screenshots/<capture>.pngwith GD, and for each width writeswebsite/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. - Demo data.
tests/Screenshots/DemoData.php:admin(),shares()(eight shares with fixed tokens, file names such asQ3-report.pdf,Contract 2026.pdf,Product photos/…, sizes, download counts and expiries, one password-protected, one expired), created through factories andShareServicewithStorage::fake('shares')so the files exist encrypted. - Screenshot tests.
tests/Screenshots/ScreenshotsTest.php, withtests/Pest.phpextended to->in('Feature', 'Browser', 'Screenshots'):beforeEach:config(['session.driver' => 'file']),travelTo('2026-10-01 09:30'), demo data; aready()wait as inSealShareTest.- One test per device and theme (four tests), each visiting the pages in turn, waiting for
networkidleand fonts (document.fonts.ready), hiding the text caret, capturing viewport-sized (not full-page) shots, and callingPublisher::publish()right after each. - The upload shot with files selected: create Livewire temporary uploads on the fake
livewire-tmpdisk and set the uploader's property through$wire.$setwithlivewire-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.
- Command.
composer.jsonscript"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. - Website scaffold.
website/:css/theme.css— tokens copied frommaterial-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 throughprefers-color-scheme.css/device-frame.css— laptop and phone frames.fonts/GoogleSansFlex-Latin.woff2,fonts/OFL.txt;img/logo.svg(fromapp-logo-icon),img/icon.png(favicon, frompublic/).
- 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),
srcsetfor both widths,loading="lazy", descriptivealt; - 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).
- 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.
- 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. - 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. - Project notes.
record-ruleforwebsite/**:website/is a faithful copy of what is live, uploaded by hand; its colours are copied frommaterial-scheme.jsonand must be copied again when the scheme is regenerated; the comparison is dated and every value sourced. And fortests/Screenshots/**: run withcomposer screenshotswhenever 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.phpguardswebsite/without a browser:- every local
src,href,srcsetentry and CSSurl()resolves to a file inwebsite/; - 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.htmlandprivacy.htmlhave 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.
- every local
- 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_finishUploadby 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 ondocument.getAnimations().length === 0before 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.cssare the guard.