6 Commits
Author SHA1 Message Date
Andreas Reinhold / reiniandClaude Opus 5 c95d0c43c2 Load the website's Plausible site script
linter / quality (push) Successful in 1m7s
tests / ci (8.5) (push) Successful in 2m16s
docker / build-and-push (push) Successful in 7m5s
docker / test (8.5) (push) Successful in 2m25s
docker / release (push) Successful in 5s
Plausible gives each new site its own script and an init call instead
of the generic script with data-domain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 16:21:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 09e24ade14 Build the amd64 image with QEMU 9.2.2 and only emulate the final stage
docker / test (8.5) (push) Successful in 2m20s
linter / quality (push) Successful in 1m5s
tests / ci (8.5) (push) Successful in 2m16s
docker / build-and-push (push) Successful in 20m13s
docker / release (push) Skipped
QEMU 8.x crashes running x86_64 programs on an arm64 host (QEMU issue
2168), which the 8.1.5 pin walked into, and 10.2 segfaults on the runner
as well; 9.2.2 runs node, composer and install-php-extensions on the
runner's host. The Composer and npm stages now build on the build
machine's platform: their output is the same for every target, so a
multi-arch build runs them once and natively.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:52:58 +02:00
Andreas Reinhold / reiniandClaude Opus 5 21bea9646d Let the admin choose one of eight colour profiles
linter / quality (push) Successful in 1m3s
tests / ci (8.5) (push) Successful in 2m8s
docker / test (8.5) (push) Successful in 2m15s
docker / build-and-push (push) Failing after 7m22s
docker / release (push) Skipped
Indigo (the default), Blue, Teal, Green, Amber, Rose and Violet in the
Vibrant style and Graphite in the Neutral style are generated from
config into the stylesheet. Admin settings opens with a colour profile
card: a swatch previews the profile on the page, and Save Settings
stores it as color_profile, which AppServiceProvider hands to the
package's resolver, so every page, mail and error page wears it. An
unknown profile is refused, and a saved one that disappears falls back
to indigo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:06:47 +02:00
Andreas Reinhold / reiniandClaude Opus 5 27e322c352 Update Livewire Material to 1.1.0
It brings colour profiles: several generated schemes under
<html data-scheme>, a resolver for the active one, and a picker. Boost's
copy of the package guideline and skill follows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:06:47 +02:00
Andreas Reinhold / reiniandClaude Opus 5 ca0dfa9396 Emulate the amd64 image with QEMU 8 on the arm64 runner
docker / test (8.5) (push) Successful in 2m9s
linter / quality (push) Successful in 1m4s
tests / ci (8.5) (push) Successful in 2m10s
docker / build-and-push (push) Failing after 9m53s
docker / release (push) Has been skipped
Recent QEMU segfaults compiling PHP extensions for amd64 on the runner's
6.8 kernel (docker/buildx#3170); QEMU 8.1.5 is the version reported to
work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 14:31:45 +02:00
Andreas Reinhold / reiniandClaude Opus 5 d860a16551 Move SealShare from GitHub to Gitea only
docker / test (8.5) (push) Successful in 2m13s
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m7s
docker / build-and-push (push) Failing after 9m54s
docker / release (push) Has been skipped
The workflows live in .gitea/workflows. The Docker workflow logs in to
the Gitea container registry with REGISTRY_TOKEN (Gitea's job token cannot
publish packages yet), publishes gitea.nonameweb.ch/nonameweb/sealshare
with a registry build cache, and makes the release on Gitea with the
version's section of the changelog as its notes.

The README, the website's quick start, both compose files and the image
label point to Gitea; the changelog announces the new image name and
links Gitea. GiteaOnlyTest keeps GitHub and ghcr.io out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 13:59:07 +02:00
35 changed files with 2794 additions and 56 deletions
+2 -2
View File
@@ -5,5 +5,5 @@ paths:
# Css
## Regenerate the colour scheme, never hand-edit it
material-scheme.css and material-scheme.json are generated together by `php artisan material:scheme "#4f46e5" --variant=vibrant` (Vibrant was chosen over Tonal Spot, which read washed out on the indigo seed). The JSON colours the Markdown mail theme and the fallback error pages, so a hand edit to the CSS alone leaves them out of step. Change the seed or variant and rerun the command instead.
## Regenerate the colour profiles, never hand-edit the scheme
material-scheme.css and material-scheme.json are generated together by `php artisan material:scheme` (no seed) from the eight `profiles` in config/livewire-material.php — all Vibrant (chosen over Tonal Spot, which read washed out on indigo) except Graphite (Neutral); `profile` is the default, indigo. The JSON colours the Markdown mail theme and the fallback error pages and lists the profiles Admin settings offers and validates against, so a hand edit to the CSS alone leaves them out of step. Change the config and rerun the command; a profile only exists once generated. The admin's choice is the `color_profile` setting, read through `Scheme::resolveProfileUsing()` in AppServiceProvider.
+1 -1
View File
@@ -6,4 +6,4 @@ paths:
# Website
## website/ is the live site, uploaded by hand
website/ is a faithful copy of sealshare.nonameweb.ch (METANET hosting), hand-written HTML/CSS with no build step, uploaded wholesale when it changes. Colours in css/theme.css are copied from resources/css/material-scheme.json — copy them again whenever the scheme is regenerated. The comparison tables are dated and every competitor value has a source from the product's own site, docs or repo; an unsourced value is "—", never a guess. Never call SealShare's encryption end-to-end (it encrypts at rest on the server). Nothing may load from another host except plausible.io. tests/Feature/WebsiteTest.php guards all of this.
website/ is a faithful copy of sealshare.nonameweb.ch (METANET hosting), hand-written HTML/CSS with no build step, uploaded wholesale when it changes. Colours in css/theme.css are copied from the indigo profile (the JSON's top-level light/dark) in resources/css/material-scheme.json — copy them again if indigo is regenerated differently; the site does not follow the admin's colour profile. The comparison tables are dated and every competitor value has a source from the product's own site, docs or repo; an unsourced value is "—", never a guess. Never call SealShare's encryption end-to-end (it encrypts at rest on the server). Nothing may load from another host except plausible.io. tests/Feature/WebsiteTest.php guards all of this.
@@ -46,6 +46,34 @@ php artisan material:scheme "#4f46e5" --variant=tonal-spot
Variants: `tonal-spot` (M3's default), `vibrant`, `expressive`, `neutral`, `fidelity`, `content`, `monochrome`, `rainbow`, `fruit-salad`. `--success`, `--warning` and `--info` set the source of the state colours; `--contrast` goes from -1 to 1. The command also writes `material-scheme.json` beside the stylesheet.
### Colour profiles
An installation that switches between several schemes lists them in `config/livewire-material.php` and runs the command without a seed, which generates every profile into the same stylesheet, keyed by `<html data-scheme>`:
```php
'profiles' => [
'indigo' => ['label' => 'Indigo', 'seed' => '#4f46e5', 'variant' => 'vibrant'],
'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'],
],
'profile' => 'indigo', // the default; else the first
```
```bash
php artisan material:scheme
```
- Names are lowercase letters, digits and dashes. Regenerate after changing the list; only generated profiles exist for the picker, the resolver and the stylesheet.
- The application says which profile is active, once, in a service provider. The closure runs every time a colour is drawn (head script, mail, error page), so it may read the database; a name that is not a generated profile, or a closure that throws, falls back to the default:
```php
use NoNameWeb\LivewireMaterial\Support\Scheme;
Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile'));
```
- `<x-theme-script>` writes the active profile to `<html data-scheme>` before the first paint; mails and error pages draw it too. `Scheme::profiles()` lists the generated profiles (name ⇒ label, light and dark roles) and `Scheme::profile()` names the active one — validate a stored choice with `Rule::in(array_keys(Scheme::profiles()))`.
- Choose with `<x-scheme-picker wire:model="colorProfile" />` (see Components). Never set `data-scheme` on an element inside the page expecting a different profile there: profiles key on `<html>`.
## Tokens
Tailwind's default palette is cleared: every colour class names an M3 role. `text-red-600`, `bg-base-200` or `text-gray-500` compile to nothing.
@@ -62,7 +90,7 @@ Tailwind's default palette is cleared: every colour class names an M3 role. `tex
## Theme
`config/livewire-material.php``theme.default` (`light`, `dark` or `system`), `theme.storage_key`, `theme.legacy_keys`. In Alpine, `$store.theme` holds `choice` (what the visitor picked), `resolved` (`light` or `dark`, what shows), `set('light'|'dark'|'system')` and `toggle()`; `x-model="$store.theme.value"` binds a control.
`config/livewire-material.php``theme.default` (`light`, `dark` or `system`), `theme.storage_key`, `theme.legacy_keys`. In Alpine, `$store.theme` holds `choice` (what the visitor picked), `resolved` (`light` or `dark`, what shows), `set('light'|'dark'|'system')` and `toggle()`; `x-model="$store.theme.value"` binds a control. With colour profiles it also holds `scheme` (the profile on screen) and `previewScheme(name)`, which shows another profile on this page without storing anything.
## Toasts
@@ -724,6 +752,14 @@ An avatar that opens a menu: `name`, `email`, `avatar` (image URL or initials; d
Switches `$store.theme`: `mode="toggle"` (default, light/dark icon button), `cycle` (light → dark → system), `picker` (segmented buttons for settings pages). Every toggle on a page shares the store.
### `<x-scheme-picker>`
A choice of colour profile (see Colour profiles): a swatch per generated profile — its name and its primary, secondary and tertiary colour — over native radios. `wire:model` or `x-model` (with `name`) binds the chosen name; choosing previews it on the page at once; storing it is the application's. `label`, `hint`, `name`, `profiles` (default `Scheme::profiles()`). A validation error for the bound property replaces the hint. Without profiles it renders nothing.
```blade
<x-scheme-picker :label="__('Colour profile')" wire:model="colorProfile" :hint="__('Applies to every page after saving')" />
```
### `<x-table>`, `<x-sort-header>`
A data table: write plain `<thead>`, `<tr>`, `<th>`, `<td>` inside `<x-table>` (`size="xs"` for a dense one); cell utilities (`text-end`, `whitespace-nowrap`) always win. Scrolling is yours: wrap it in `<div class="overflow-x-auto">`. A row that opens something is `data-list-row` with one `data-list-open` control; a selected row is `aria-selected="true"`.
+1 -1
View File
@@ -21,7 +21,7 @@ docker-compose*.yml
Dockerfile
# CI/CD
.github
.gitea
# Testing
tests
+1 -1
View File
@@ -8,4 +8,4 @@
CHANGELOG.md export-ignore
README.md export-ignore
.github/workflows/browser-tests.yml export-ignore
.gitea export-ignore
@@ -67,25 +67,33 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6
# The runner is an arm64 server, so the amd64 image's final stage is emulated. QEMU 8.x
# crashes running x86_64 programs on an arm64 host (QEMU issue 2168, "QEMU internal
# SIGSEGV {code=MAPERR, addr=0x20}") and 10.2 segfaults on this runner too; 9.2.2 was
# checked on the runner's host: node, composer and install-php-extensions all run.
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:qemu-v9.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
# Gitea's job token cannot publish packages yet: REGISTRY_TOKEN is an access token with
# package write rights, owned by the account that pushes (gitea.actor).
- name: Log in to the Gitea container registry
if: gitea.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: gitea.nonameweb.ch
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
images: gitea.nonameweb.ch/nonameweb/sealshare
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -98,21 +106,27 @@ jobs:
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
push: ${{ gitea.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: type=registry,ref=gitea.nonameweb.ch/nonameweb/sealshare:buildcache
cache-to: ${{ gitea.event_name != 'pull_request' && 'type=registry,ref=gitea.nonameweb.ch/nonameweb/sealshare:buildcache,mode=max' || '' }}
release:
runs-on: ubuntu-latest
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(gitea.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
- name: Take the release notes from the changelog
run: |
version="${{ gitea.ref_name }}"
awk -v heading="## [${version#v}]" 'index($0, heading) == 1 { found = 1; next } found && /^## \[/ { exit } found { print }' CHANGELOG.md > release-notes.md
test -s release-notes.md
- name: Create the Gitea release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
generate_release_notes: true
body_path: release-notes.md
@@ -44,4 +44,4 @@ jobs:
# commit_options: '--no-verify'
# file_pattern: |
# **/*
# !.github/workflows/*
# !.gitea/workflows/*
+4 -6
View File
@@ -11,11 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Eight colour profiles — Indigo (the default), Blue, Teal, Green, Amber, Rose, Violet and Graphite. An admin picks one in Admin settings, previews it on the page, and after saving every page, mail and error page uses it; light and dark stay each visitor's own choice.
- The share created page offers the link as a QR code: "Show QR code" opens it in a dialog (full screen on a phone) and "Download" saves it as a PNG. For a password-protected share the dialog reminds that recipients also need the password; the code holds only the link.
- A "Share…" button on the same page opens the device's share sheet with the link, where the browser has one (mostly phones and Safari).
### Changed
- SealShare moved from GitHub to Gitea: the source is at https://gitea.nonameweb.ch/noNameWEB/SealShare, and the Docker image is published as `gitea.nonameweb.ch/nonameweb/sealshare`. Images at `ghcr.io/surtic86/sealshare` are no longer updated — change `image:` in your `docker-compose.yml` to the new name to keep receiving releases.
- The interface is rebuilt on [Livewire Material](https://gitea.nonameweb.ch/noNameWEB/livewire-material), a Material 3 Expressive component library, replacing Mary UI and DaisyUI. Every page — upload, share created, download, sign-in, settings, admin and the setup wizard — uses its components, in a colour scheme generated from SealShare's indigo.
- The theme follows the system's light or dark setting until a user picks Light, Dark or System in Settings → Appearance, or from the account menu. A theme chosen in 1.x is kept.
- A floating toolbar centred at the bottom of every page replaces the sidebar and header layouts. Signed-in users reach Upload and the admin pages from it, and Settings, the theme and Log out from its account menu. The site's name and logo head the upload and download pages.
@@ -105,9 +107,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dark themed UI built with Livewire, Alpine.js, Tailwind CSS and DaisyUI.
- Docker images published to `ghcr.io/surtic86/sealshare`, served by FrankenPHP via Laravel Octane.
[Unreleased]: https://github.com/surtic86/SealShare/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/surtic86/SealShare/compare/v1.2.0...v2.0.0
[1.2.0]: https://github.com/surtic86/SealShare/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/surtic86/SealShare/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/surtic86/SealShare/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/surtic86/SealShare/releases/tag/v1.0.0
[Unreleased]: https://gitea.nonameweb.ch/noNameWEB/SealShare/compare/v2.0.0...main
[2.0.0]: https://gitea.nonameweb.ch/noNameWEB/SealShare/releases/tag/v2.0.0
+1 -1
View File
@@ -195,7 +195,7 @@ This application uses `nonameweb/livewire-material`: Material 3 Expressive compo
- Components are anonymous Blade components, unprefixed unless `config/livewire-material.php` sets a `prefix`. Before writing or changing a view that uses them, activate the `livewire-material-development` skill for the props, slots and traps of each component.
- Never write maryUI tags (`<x-mary-*>`) or daisyUI classes (`btn`, `card`, `badge`, `bg-base-200`, `text-base-content`…). They compile to nothing and fail silently.
- Every layout includes `<x-theme-script />` in `<head>` before `@vite`. The colour scheme is generated with `php artisan material:scheme` — never edit `resources/css/material-scheme.css` by hand.
- Every layout includes `<x-theme-script />` in `<head>` before `@vite`. The colour scheme is generated with `php artisan material:scheme` — never edit `resources/css/material-scheme.css` by hand. With colour profiles (`livewire-material.profiles`), run it without a seed after changing them; the active profile comes from `Scheme::resolveProfileUsing()`.
- While the application runs locally, every token and component renders in the application's own scheme at `/material` (the showcase).
- HTTP error pages and the Markdown mail theme come from the package. Change error wording by publishing `--tag=livewire-material-errors`; select the mail theme with `MAIL_MARKDOWN_THEME=livewire-material::mail.theme`.
+8 -5
View File
@@ -1,7 +1,9 @@
# ============================================
# Stage 1: Install PHP dependencies
# ============================================
FROM composer:2 AS vendor
# Built on the build machine's own platform: vendor/ is plain PHP, the same for every target, so a
# multi-arch build runs it once and never under emulation.
FROM --platform=$BUILDPLATFORM composer:2 AS vendor
WORKDIR /app
@@ -21,8 +23,9 @@ RUN composer dump-autoload --optimize --no-dev
# ============================================
# Stage 2: Build frontend assets
# ============================================
# After Composer: the stylesheet and script import Livewire Material from vendor/.
FROM node:24-alpine AS assets
# After Composer: the stylesheet and script import Livewire Material from vendor/. On the build
# machine's platform too: the output is CSS and JavaScript, whatever the target.
FROM --platform=$BUILDPLATFORM node:24-alpine AS assets
WORKDIR /app
@@ -41,7 +44,7 @@ RUN npm run build
FROM dunglas/frankenphp:php8.5-alpine AS production
LABEL maintainer="surtic86"
LABEL org.opencontainers.image.source="https://github.com/surtic86/SealShare"
LABEL org.opencontainers.image.source="https://gitea.nonameweb.ch/noNameWEB/SealShare"
LABEL org.opencontainers.image.description="Self-hosted encrypted file sharing"
# Install required PHP extensions
@@ -84,7 +87,7 @@ COPY --from=vendor /app/vendor ./vendor
COPY --from=assets /app/public/build ./public/build
# Remove dev/build files and stale cache not needed in production
RUN rm -rf node_modules tests .github docker/dev.Dockerfile docker/dev-entrypoint.sh .env .env.example \
RUN rm -rf node_modules tests .gitea docker/dev.Dockerfile docker/dev-entrypoint.sh .env .env.example \
bootstrap/cache/*.php \
&& mkdir -p storage/app/shares storage/app/public storage/framework/cache \
storage/framework/sessions storage/framework/testing storage/framework/views \
+4 -3
View File
@@ -27,6 +27,7 @@ A simple, self-hosted file sharing solution built with Laravel. Upload files, ge
- **Admin Dashboard** — View, manage, and delete all shares
- **Admin Settings** — Configure upload limits, storage quotas, branding, and more
- **Site Branding** — Custom logo, title, and description
- **Colour Profiles** — Eight Material 3 colour profiles (Indigo, Blue, Teal, Green, Amber, Rose, Violet, Graphite); the admin picks one for every page, mail and error page
- **System Password** — Optional global password gate to restrict upload access
- **User Authentication** — Login, password reset, email verification
- **Two-Factor Authentication** — TOTP-based 2FA via Laravel Fortify
@@ -68,11 +69,11 @@ The app is available at `http://localhost:8000` with Vite HMR on port `5173`.
```bash
mkdir sealshare && cd sealshare
curl -O https://raw.githubusercontent.com/surtic86/SealShare/main/docker-compose.example.yml
curl -O https://gitea.nonameweb.ch/noNameWEB/SealShare/raw/branch/main/docker-compose.example.yml
cp docker-compose.example.yml docker-compose.yml
# Generate an app key and paste it into docker-compose.yml
docker run --rm ghcr.io/surtic86/sealshare:latest php artisan key:generate --show
docker run --rm gitea.nonameweb.ch/nonameweb/sealshare:latest php artisan key:generate --show
# Edit docker-compose.yml — set APP_KEY, APP_URL, and SERVER_NAME
# Then start:
@@ -114,7 +115,7 @@ Behind a reverse proxy, raise its request body limit and read timeout as well (n
### Manual (without Docker)
```bash
git clone https://github.com/surtic86/SealShare.git
git clone https://gitea.nonameweb.ch/noNameWEB/SealShare.git
cd SealShare
composer install --no-dev --optimize-autoloader
+8
View File
@@ -5,10 +5,12 @@ namespace App\Livewire\Admin;
use App\Models\Setting;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Livewire\WithFileUploads;
use NoNameWeb\LivewireMaterial\Concerns\Toasts;
use NoNameWeb\LivewireMaterial\Support\Scheme;
#[Layout('layouts.app')]
class AdminSettings extends Component
@@ -16,6 +18,9 @@ class AdminSettings extends Component
use Toasts;
use WithFileUploads;
/** The colour profile every page, mail and error page wears (config/livewire-material.php). */
public string $colorProfile = '';
public string $systemPassword = '';
public string $defaultExpiration = '';
@@ -44,6 +49,7 @@ class AdminSettings extends Component
public function mount(): void
{
$this->colorProfile = Scheme::profile() ?? '';
$this->defaultExpiration = Setting::get('default_expiration', '') ?? '';
$this->maxFileSize = min(
(int) Setting::get('max_file_size', 100 * 1024 * 1024) / (1024 * 1024),
@@ -83,6 +89,7 @@ class AdminSettings extends Component
$phpMaxMb = self::phpMaxUploadMb();
$this->validate([
'colorProfile' => ['required', 'string', Rule::in(array_keys(Scheme::profiles()))],
'maxFileSize' => ['required', 'integer', 'min:1', 'max:'.$phpMaxMb],
'maxStorageQuota' => ['required', 'integer', 'min:1'],
'maxFilesPerShare' => ['required', 'integer', 'min:1'],
@@ -98,6 +105,7 @@ class AdminSettings extends Component
Setting::set('system_password', Hash::make($this->systemPassword));
}
Setting::set('color_profile', $this->colorProfile);
Setting::set('default_expiration', $this->defaultExpiration ?: null);
Setting::set('max_file_size', $this->maxFileSize * 1024 * 1024);
Setting::set('max_storage_quota', $this->maxStorageQuota * 1024 * 1024 * 1024);
+6
View File
@@ -2,11 +2,13 @@
namespace App\Providers;
use App\Models\Setting;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
use NoNameWeb\LivewireMaterial\Support\Scheme;
class AppServiceProvider extends ServiceProvider
{
@@ -24,6 +26,10 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
$this->configureDefaults();
// The colour profile the admin chose in Admin settings; asked each time a page, mail or
// error page draws its colours, so a new choice applies at once in every Octane worker.
Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile'));
}
/**
Generated
+3 -3
View File
@@ -2999,11 +2999,11 @@
},
{
"name": "nonameweb/livewire-material",
"version": "1.0.1",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://gitea.nonameweb.ch/noNameWEB/livewire-material.git",
"reference": "7798352cfc71643a88d4658f9704e65c4e24e8da"
"reference": "d805dbc4076dc06c4d7e5023f99be4c420219975"
},
"require": {
"laravel/framework": "^13.0",
@@ -3083,7 +3083,7 @@
"material-design",
"tailwindcss"
],
"time": "2026-09-13T09:16:52+00:00"
"time": "2026-09-13T12:39:26+00:00"
},
{
"name": "nunomaduro/termwind",
+26
View File
@@ -107,6 +107,32 @@ return [
'scheme' => resource_path('css/material-scheme.json'),
/*
|--------------------------------------------------------------------------
| Colour profiles
|--------------------------------------------------------------------------
|
| The profiles an admin chooses between in Admin settings. Each one is a
| 'label', a 'seed' (#rrggbb), a 'variant' and an optional 'contrast'.
| `php artisan material:scheme` (without a seed) generates them all into
| resources/css/material-scheme.css; regenerate after changing this list.
| 'profile' is the default, until an admin chooses.
|
*/
'profiles' => [
'indigo' => ['label' => 'Indigo', 'seed' => '#4f46e5', 'variant' => 'vibrant'],
'blue' => ['label' => 'Blue', 'seed' => '#0b57d0', 'variant' => 'vibrant'],
'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'],
'green' => ['label' => 'Green', 'seed' => '#2e7d32', 'variant' => 'vibrant'],
'amber' => ['label' => 'Amber', 'seed' => '#e8710a', 'variant' => 'vibrant'],
'rose' => ['label' => 'Rose', 'seed' => '#c2185b', 'variant' => 'vibrant'],
'violet' => ['label' => 'Violet', 'seed' => '#6750a4', 'variant' => 'vibrant'],
'graphite' => ['label' => 'Graphite', 'seed' => '#5f6368', 'variant' => 'neutral'],
],
'profile' => 'indigo',
/*
|--------------------------------------------------------------------------
| Mail
+2 -2
View File
@@ -18,7 +18,7 @@ services:
# SealShare Application (FrankenPHP/Octane)
# ------------------------------------------
app:
image: ghcr.io/surtic86/sealshare:latest
image: gitea.nonameweb.ch/nonameweb/sealshare:latest
restart: unless-stopped
ports:
- "80:80" # HTTP
@@ -71,7 +71,7 @@ services:
# Scheduler - Runs cleanup for expired shares
# ------------------------------------------
scheduler:
image: ghcr.io/surtic86/sealshare:latest
image: gitea.nonameweb.ch/nonameweb/sealshare:latest
restart: unless-stopped
entrypoint: ["php", "artisan", "schedule:work"]
volumes:
+2 -2
View File
@@ -1,6 +1,6 @@
services:
app:
image: ghcr.io/surtic86/sealshare:latest
image: gitea.nonameweb.ch/nonameweb/sealshare:latest
build:
context: .
dockerfile: Dockerfile
@@ -47,7 +47,7 @@ services:
retries: 3
scheduler:
image: ghcr.io/surtic86/sealshare:latest
image: gitea.nonameweb.ch/nonameweb/sealshare:latest
build:
context: .
dockerfile: Dockerfile
+210
View File
@@ -0,0 +1,210 @@
# Colour profiles
## Goal
An installation of SealShare can wear one of eight colour profiles instead of the single indigo
scheme. The admin picks the profile in Admin settings, previews it on the page while choosing, and
on Save it applies to everyone: signed-in users, recipients on the upload and download pages, the
Markdown mails and the error pages. Livewire Material learns colour profiles in general — any
application lists its own in config, the package generates them, switches between them before the
first paint and follows the active one everywhere it draws colour — and ships it as 1.1.0 before
SealShare tags 2.0.0.
## Context
**Livewire Material 1.0.1** (`../livewire-material`):
- `php artisan material:scheme {seed} --variant= --contrast= --success= --warning= --info= --output=`
(`src/Console/SchemeCommand.php`) runs `resources/node/scheme.mjs` (Google's
material-color-utilities, a 93 KB bundle) through Node and writes `resources/css/material-scheme.css`
`:root, [data-theme='light'] { color-scheme: light; --md-sys-color-*: … }` and
`[data-theme='dark'] { … }`, about 60 roles each — and `material-scheme.json`
(`{seed, variant, spec, contrast, light, dark}`).
- Every component and token reads only `--md-sys-color-*` (`resources/css/tokens/theme.css` maps them
to Tailwind colours). The package's own default is `resources/css/tokens/scheme.css` and `.json`.
- `<x-theme-script>` (in `<head>`, before `@vite`) writes `data-theme`, `data-theme-choice`,
`data-theme-key`, `data-rail`, `data-rail-key` on `<html>` before the first paint, and puts them
back after a `wire:navigate` swap (`onSwap`). `$store.theme` lives in `resources/js/theme.js`.
- `Support\Scheme::load()` / `light()` read `config('livewire-material.scheme')` (the JSON) merged over
the package default; the mail theme (`resources/views/mail/theme.blade.php`) and the fallback styles
of the error pages (`Support\ErrorPage::fallbackStyles()`) use it.
- Tests: `SchemeCommandTest` (runs Node), `TokensTest`, `MailThemeTest`, `ErrorPagesTest`,
`ShowcaseTest`, browser tests in three engines; CI on Gitea.
**SealShare:**
- One scheme, `#4f46e5` Vibrant (`.ai/rules/css.md` records the exact command).
- The production Docker image has no Node, so nothing can be generated at runtime.
- Settings → Appearance is the Light/Dark/System picker, stored per browser. All users are admins;
recipients are guests.
- `App\Livewire\Admin\AdminSettings` holds the settings in `Setting` (key/value) and saves them in
`saveSettings()` with one validation call; the form ends in "Save Settings". It uses `Toasts`.
- Octane: the application boots once per worker, so anything request-specific must be read per call.
## Decisions
- **The admin chooses, nobody else** — one profile for the whole installation; no per-user or
per-visitor choice. Light, dark and system stay each visitor's own, as now.
- **Ready-made profiles, no free colour** — generated ahead of time with `material:scheme` and shipped
in the CSS: correct from the first frame, no generator in the browser, mails and error pages can
follow.
- **Eight profiles, Vibrant style like today**`indigo` Indigo `#4f46e5` (the default, today's),
`blue` Blue `#0b57d0`, `teal` Teal `#00897b`, `green` Green `#2e7d32`, `amber` Amber `#e8710a`,
`rose` Rose `#c2185b`, `violet` Violet `#6750a4`, all `vibrant`; `graphite` Graphite `#5f6368`
in the `neutral` style.
- **The mechanism is the package's, the profiles are the application's** — Livewire Material gets
`profiles` in its config; SealShare lists its eight in its published config. Other applications
define their own.
- **Swatch picker at the top of Admin settings, previewed live, applied on Save** — a "Colour
profile" card with one swatch per profile (primary, secondary and tertiary dots, the name, a check
on the chosen one); a click recolours the page at once; "Save Settings" stores it for everyone.
Leaving without saving shows the saved profile on the next page.
- **Profiles are keyed by `<html data-scheme>`** — the default profile also stands without the
attribute, so the stylesheet works before the head script runs and with an unknown name:
```css
:root, [data-theme='light'] { /* default, light */ }
[data-theme='dark'] { /* default, dark */ }
[data-scheme='teal'], [data-scheme='teal'][data-theme='light'] { /* teal, light */ }
[data-scheme='teal'][data-theme='dark'] { /* teal, dark */ }
```
A profile's two-attribute selectors outrank the default's single ones, and its one-attribute
selector comes later in the file than `:root`, so the order is part of the format.
- **The active profile is resolved on every use, never kept** — the application registers a resolver
once (`Scheme::resolveProfileUsing(fn (): ?string => …)`); the head script, the mail theme and the
error pages call it each time they draw. A name that is not a generated profile, or no resolver,
falls back to the JSON's `default` — the `profile` config (else the first profile) when the scheme
was generated. Nothing request-specific is
stored on a static, so Octane workers stay clean.
- **The JSON keeps its old top-level shape**`light` and `dark` are still the default profile's
roles, beside `default` and `profiles.{name}.{label, seed, variant, spec, contrast, light, dark}`,
so a reader of the 1.0 format keeps working.
- **`material:scheme` with a seed is unchanged** — one scheme, as in 1.0. Without a seed it generates
every configured profile; without either it fails with a message naming both ways.
- **A `<x-scheme-picker>` component in the package** — native radios in a `radiogroup`, bound with
`wire:model` (or `x-model`), each labelled with the profile's name and its three colours from the
JSON; choosing one sets `<html data-scheme>` immediately (the preview). Errors for the bound property
show under it.
- **The showcase can preview every profile** — a profile menu in its app bar when profiles are
configured, recolouring the showcase without storing anything.
- **Release** — Livewire Material 1.1.0 (a feature), then SealShare's lock, all before 2.0.0.
SealShare's changelog lists it under 2.0.0 "Added".
## Out of scope
- A colour picker for any colour, extracting a colour from the logo, or per-profile contrast levels.
- Per-user or per-visitor profiles, or a profile switch outside Admin settings.
- Changing the website's colours (it stays indigo) or adding profile screenshots.
- New success/warning/info sources per profile — they stay the package defaults.
## Implementation steps
### Livewire Material 1.1.0 (`../livewire-material`)
1. **Config.** `config/livewire-material.php`: `'profiles' => []` (name ⇒ `label`, `seed`,
`variant`, optional `contrast`) and `'profile' => null` (the fallback name), documented in the
config comment beside `scheme`.
2. **Generator.** `SchemeCommand`: `seed` becomes optional. Without it, read `profiles`; for each
run `scheme.mjs` as today (validating seed, variant and contrast through the generator's own
errors), then write the stylesheet in the format under Decisions — the default profile (the
`profile` config, else the first) as the plain blocks, then every profile's blocks in config
order — and the JSON with `default`, `profiles` and the default's top-level `light`/`dark`. The
header comment names the command and says the profiles come from config. With neither a seed nor
profiles, fail naming both.
3. **Scheme.** `Support\Scheme`: `resolveProfileUsing(?Closure $resolver): void`,
`profiles(?string $path = null): array` (name ⇒ label and light/dark roles, from the JSON),
`profile(?string $path = null): ?string` (the resolver's answer if it names a profile in the
JSON, else `default` from the JSON, else null), and `load(?string $path = null, ?string $profile = null)`
returning that profile's roles merged over the package default (the active profile when
`$profile` is null; the top-level roles for a 1.0 file). `light()` follows, so the mail theme and
`ErrorPage::fallbackStyles()` draw the active profile without further change: the fallback's plain
`:root`/`[data-theme]` blocks carry that profile's roles, which is all a page without its build
needs. Every method reads the JSON on each call, as `load()` does today.
4. **Head script.** `<x-theme-script>`: when the JSON has profiles, write
`data-scheme="{active profile}"` on `<html>` with the others, and keep it through `onSwap`.
`$store.theme` gains `scheme` (read from the attribute) and `previewScheme(name)` (sets the
attribute, stores nothing).
5. **Picker.** `resources/views/components/scheme-picker.blade.php` as under Decisions: props
`label`, `hint`, `profiles` (default `Scheme::profiles()`), `name`; labels through `__()`. Each
swatch is a label around a visually hidden native radio, drawn with Tailwind utilities (a
`surface-container` tile, `outline` when checked, a check icon); its three dots are the only inline
styles — `background-color` from that profile's light roles, which `Scheme` has already checked
are `#rrggbb` — because they show another profile's colours than the page's. `x-on:change` calls
`$store.theme.previewScheme($event.target.value)`. With no profiles it renders nothing.
6. **Showcase.** A profile menu in `resources/views/showcase/layout.blade.php`'s app bar when profiles
exist, calling `previewScheme`; the colour section already reads the variables, so it follows.
`src/Showcase/Sections.php` gains the picker as an example (and the search index with it).
7. **Docs.** `resources/boost/skills/livewire-material-development/SKILL.md` (Colour scheme: profiles,
resolver, picker; the new component in Components), `resources/boost/guidelines/core.blade.php`
(one line), `README.md` (Colour scheme and Configuration).
8. **Release.** Verify in `.verify` (Feature + Browser in chrome, firefox, safari), push, watch CI,
tag `1.1.0`.
### SealShare
9. **Package.** `composer update nonameweb/livewire-material` to 1.1.0.
10. **Profiles.** `config/livewire-material.php`: the eight profiles under Decisions and
`'profile' => 'indigo'`. Run `php artisan material:scheme` to regenerate
`resources/css/material-scheme.css` and `.json`; `npm run build`. Update `.ai/rules/css.md`: the
scheme is regenerated with `php artisan material:scheme` from the profiles in config, never
hand-edited.
11. **Resolver.** `AppServiceProvider::boot()`: `Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile'))`.
12. **Admin settings.** `AdminSettings`: `public string $colorProfile`, mounted from
`Scheme::profile()`; validated with `Rule::in(array_keys(Scheme::profiles()))` in
`saveSettings()` — the profiles actually generated into the stylesheet, not merely listed in
config; saved with `Setting::set('color_profile', $this->colorProfile)`.
`admin-settings.blade.php`: a "Colour profile" card first in the form with
`<x-scheme-picker wire:model="colorProfile" :label="__('Colour profile')" />` and a hint that
the choice applies to every page, mail and error page after saving.
13. **Docs.** README features: "Colour Profiles — eight colour profiles, chosen by the admin".
CHANGELOG `2.0.0` "Added". `composer screenshots` again (the admin settings shot shows the new
card); the website's feature list gains the same line.
## Testing
**Package**
- `SchemeCommandTest`: with profiles configured and no seed, the JSON has `default` and every profile
with light and dark roles, top-level `light`/`dark` equal the default's; the stylesheet has the
default's plain blocks first and each profile's `[data-scheme='…']` blocks after, with its hexes;
a seed still writes the 1.0 format; neither fails with the message.
- `SchemeTest` (new, Feature): `profile()` follows a resolver naming a profile, falls back on an
unknown name, on no resolver and on a 1.0 file; `load()` returns the chosen profile's roles.
- `MailThemeTest`: the mail's primary is the resolved profile's. `ErrorPagesTest`: the fallback
styles carry the resolved profile's roles.
- Components: `<x-theme-script>` renders `data-scheme` for the resolved profile and none without
profiles; `<x-scheme-picker>` renders a radio per profile, checked from `wire:model`, with the
labels.
- Browser (three engines): `--md-sys-color-primary` on `<html>` is the profile's in light and in
dark, and the default's without the attribute; choosing a swatch changes it at once; the attribute
survives `wire:navigate`; the showcase menu previews a profile.
**SealShare**
- `AdminSettingsTest`: a valid profile is saved and a toast dispatched; an unknown one fails
validation and saves nothing.
- `ColourProfileTest` (new, Feature): a guest's upload page renders `data-scheme` from the saved
setting and the default without one; the reset-password mail uses the profile's primary.
- `tests/Browser/SealShareTest.php`: in Admin settings a swatch recolours the page before saving;
after Save and a reload, and on a guest's download page, the profile stays.
- `DesignLanguageTest` and `WebsiteTest` keep passing.
## Risks and open questions
- **Stylesheet size.** Eight profiles × two themes × ~60 roles is about 60 KB before compression
(a few KB gzipped); acceptable, and the CSS stays cacheable.
- **A query per page for the setting.** `Setting::get('color_profile')` runs when the head script
renders, like the site title already does; cache it later if it ever shows.
- **Swatch colours are inline styles.** The design guard does not look at `style` attributes, so
nothing stops them spreading; they stay inside `<x-scheme-picker>` and come only from `Scheme`'s
checked hexes, which the component test asserts.
- **A profile removed from config** while saved leaves the setting pointing nowhere; the resolver's
fallback to the default covers it, and Admin settings shows the default as chosen.
- **Config and stylesheet out of step.** A profile added to config but not generated is not offered:
the picker, the resolver and the validation all read the generated JSON. `.ai/rules/css.md` says to
regenerate after changing profiles.
- **Open tabs** keep the profile they loaded (or previewed) until their next full load;
`wire:navigate` carries the page's current attribute forward.
- **Error pages without a build** use the fallback styles, which draw the active profile directly;
covered by `ErrorPagesTest`.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,10 @@
<h1 class="mb-6 type-headline-md">{{ __('System Settings') }}</h1>
<form wire:submit="saveSettings" class="grid gap-6">
<x-card :title="__('Colour profile')" variant="outlined">
<x-scheme-picker wire:model="colorProfile" :hint="__('Choosing one previews it here. After saving, every page, mail and error page uses it.')" data-test="color-profile" />
</x-card>
<x-card :title="__('Branding')" variant="outlined">
<div class="grid gap-5">
<x-input wire:model="siteTitle" :label="__('Site Title')" :hint="__('Displayed as the heading on the upload page.')" />
@@ -99,7 +103,7 @@
/>
</x-card>
<x-button type="submit" :label="__('Save Settings')" variant="filled" icon="check" spinner="saveSettings" class="w-full" />
<x-button type="submit" :label="__('Save Settings')" variant="filled" icon="check" spinner="saveSettings" class="w-full" data-test="save-settings" />
</form>
<x-modal wire:model="confirmingLogoRemoval" :title="__('Remove the logo?')" icon="delete">
+24
View File
@@ -1,10 +1,12 @@
<?php
use App\Models\Setting;
use App\Models\Share;
use App\Models\User;
use App\Services\ShareService;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use NoNameWeb\LivewireMaterial\Support\Scheme;
/**
* A page of SealShare, once it can be used: loaded, with Alpine and Livewire started.
@@ -152,3 +154,25 @@ test('a first visit follows the system theme, and Appearance switches it', funct
->assertScript("document.documentElement.dataset.theme === 'light'")
->assertScript("localStorage.getItem('sealshare-theme') === 'light'");
});
test('an admin previews a colour profile, saves it, and every page wears it', function () {
$this->actingAs(User::factory()->admin()->create());
$page = ready(visit('/admin/settings'));
$page->assertScript("document.documentElement.getAttribute('data-scheme') === 'indigo'")
->click('[data-test="color-profile"] [data-scheme-option="teal"]')
->assertScript("document.documentElement.getAttribute('data-scheme') === 'teal'");
expect(Setting::get('color_profile'))->toBeNull();
$page->click('[data-test="save-settings"]')
->assertSee('Settings saved successfully.');
expect(Setting::get('color_profile'))->toBe('teal');
ready(visit('/upload'))
->assertScript("document.documentElement.getAttribute('data-scheme') === 'teal'")
->assertScript("getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-primary').trim() === '".Scheme::profiles()['teal']['light']['primary']."'")
->assertNoJavaScriptErrors();
});
+29
View File
@@ -128,3 +128,32 @@ test('admin can remove the logo through its dialog', function () {
expect(Setting::get('site_logo'))->toBeNull();
Storage::disk('public')->assertMissing($path);
});
test('admin chooses the colour profile, starting from the saved one', function () {
$admin = User::query()->where('is_admin', true)->first();
Livewire::actingAs($admin)
->test(AdminSettings::class)
->assertSet('colorProfile', 'indigo')
->set('colorProfile', 'teal')
->call('saveSettings')
->assertHasNoErrors();
expect(Setting::get('color_profile'))->toBe('teal');
Livewire::actingAs($admin)
->test(AdminSettings::class)
->assertSet('colorProfile', 'teal');
});
test('a colour profile that was not generated is refused', function () {
$admin = User::query()->where('is_admin', true)->first();
Livewire::actingAs($admin)
->test(AdminSettings::class)
->set('colorProfile', 'ocean')
->call('saveSettings')
->assertHasErrors(['colorProfile' => 'in']);
expect(Setting::get('color_profile'))->toBeNull();
});
+36
View File
@@ -0,0 +1,36 @@
<?php
use App\Models\Setting;
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use NoNameWeb\LivewireMaterial\Support\Scheme;
test('every page wears the colour profile the admin saved, indigo until then', function () {
$this->get(route('upload'))->assertOk()->assertSee('({"scheme":"indigo",', false);
Setting::set('color_profile', 'graphite');
$this->get(route('upload'))->assertSee('({"scheme":"graphite",', false);
$this->get('/s/does-not-exist')->assertNotFound()->assertSee('({"scheme":"graphite",', false);
});
test('a saved profile that no longer exists falls back to the default', function () {
Setting::set('color_profile', 'ocean');
expect(Scheme::profile())->toBe('indigo');
$this->get(route('upload'))->assertSee('({"scheme":"indigo",', false);
});
test('mails take the saved colour profile', function () {
Setting::set('color_profile', 'rose');
$html = (string) (new ResetPassword('token'))->toMail(User::factory()->create())->render();
expect($html)->toContain('background-color: '.Scheme::profiles()['rose']['light']['primary'])
->not->toContain('background-color: '.Scheme::profiles()['indigo']['light']['primary']);
});
test('the stylesheet carries all eight profiles', function () {
expect(array_keys(Scheme::profiles()))->toBe(['indigo', 'blue', 'teal', 'green', 'amber', 'rose', 'violet', 'graphite'])
->and(file_get_contents(resource_path('css/material-scheme.css')))->toContain("[data-scheme='graphite'][data-theme='dark']");
});
+44
View File
@@ -0,0 +1,44 @@
<?php
/*
* SealShare lives on Gitea only: the source at gitea.nonameweb.ch/noNameWEB/SealShare and the image
* at gitea.nonameweb.ch/nonameweb/sealshare. The changelog keeps its history, and the website's
* comparison sources link other projects' repositories, so neither is checked.
*/
test('the project points to Gitea, not GitHub, for its source, image and workflows', function () {
$files = [
'README.md',
'Dockerfile',
'.dockerignore',
'.gitattributes',
'docker-compose.yml',
'docker-compose.example.yml',
...array_map(fn (string $path): string => str($path)->after(base_path().'/')->toString(), glob(base_path('.gitea/workflows/*.yml'))),
];
foreach ($files as $file) {
expect(file_get_contents(base_path($file)))->not->toMatch('/github|ghcr\.io/i', "{$file} still refers to GitHub");
}
expect(base_path('.github'))->not->toBeDirectory()
->and(glob(base_path('.gitea/workflows/*.yml')))->not->toBeEmpty();
});
test('the website installs SealShare from Gitea', function () {
preg_match('/<section[^>]*id="install".*?<\/section>/s', file_get_contents(base_path('website/index.html')), $install);
expect($install[0])->not->toMatch('/github|ghcr\.io/i')
->toContain('gitea.nonameweb.ch/nonameweb/sealshare:latest')
->toContain('https://gitea.nonameweb.ch/noNameWEB/SealShare/raw/branch/main/docker-compose.example.yml');
});
test('images are published to the Gitea registry and releases are made on Gitea', function () {
$workflow = file_get_contents(base_path('.gitea/workflows/docker.yml'));
expect($workflow)
->toContain('registry: gitea.nonameweb.ch')
->toContain('images: gitea.nonameweb.ch/nonameweb/sealshare')
->toContain('password: ${{ secrets.REGISTRY_TOKEN }}')
->toContain('uses: https://gitea.com/actions/gitea-release-action@v1');
});
+2 -1
View File
@@ -107,7 +107,8 @@ test('the pages are titled, described and in English', function () {
->toContain('<html lang="en"')
->toMatch('/<title>[^<]+<\/title>/')
->toMatch('/<meta name="description" content="[^"]+"/')
->toContain('data-domain="sealshare.nonameweb.ch"');
->toContain('<script async src="https://plausible.io/js/pa-zWB4R2-rrBeLfwms3uIBT.js"></script>')
->toContain('plausible.init()');
}
});
+4 -3
View File
@@ -2,9 +2,10 @@
* SealShare website theme.
*
* The colours are not an approximation of the app's: they are copied from
* resources/css/material-scheme.json, generated by `php artisan material:scheme "#4f46e5"
* --variant=vibrant` (Material 3, spec 2025, contrast 0). When the scheme is regenerated, copy the
* roles below again the site does not read the file. Light is the default, dark follows the
* the indigo profile in resources/css/material-scheme.json (seed #4f46e5, Vibrant, Material 3 spec
* 2025, contrast 0; the file's top-level light and dark), generated by `php artisan material:scheme`
* from the app's colour profiles. When indigo is regenerated differently, copy the roles below again
* the site does not read the file, and does not follow the colour profile an admin chooses. Light is the default, dark follows the
* visitor's system setting; the site has no theme toggle of its own.
*
* Shared by index.html and privacy.html.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+9 -4
View File
@@ -12,7 +12,12 @@
<meta property="og:title" content="SealShare — your own secure upload platform">
<meta property="og:description" content="SealShare is self-hosted file sharing: your company&#x27;s own upload platform to exchange files with customers, encrypted at rest, on your own server and domain. Open source, one Docker image.">
<meta property="og:image" content="https://sealshare.nonameweb.ch/img/screenshots/desktop/light/01-upload-1600.webp">
<script defer data-domain="sealshare.nonameweb.ch" src="https://plausible.io/js/script.js"></script>
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://plausible.io/js/pa-zWB4R2-rrBeLfwms3uIBT.js"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
<link rel="icon" href="img/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="img/icon.png">
<link rel="preload" href="fonts/GoogleSansFlex-Latin.woff2" as="font" type="font/woff2" crossorigin>
@@ -253,7 +258,7 @@
<article class="feature">
<span class="badge-icon badge-icon--tertiary" aria-hidden="true"><svg viewBox="0 0 100 100"><use href="#s-cookie-6"/></svg><svg class="icon" aria-hidden="true"><use href="#i-palette"/></svg></span>
<h3>Your branding</h3>
<p>Your logo, site title and description on the upload page.</p>
<p>Your logo, site title and description on the upload page, in one of eight colour profiles.</p>
</article>
<article class="feature">
<span class="badge-icon" aria-hidden="true"><svg viewBox="0 0 100 100"><use href="#s-cookie-6"/></svg><svg class="icon" aria-hidden="true"><use href="#i-shield-lock"/></svg></span>
@@ -547,11 +552,11 @@
<div class="code">
<div class="code__bar"><svg class="icon" aria-hidden="true"><use href="#i-terminal"/></svg><span>Production, with Docker Compose</span></div>
<pre><code>mkdir sealshare &amp;&amp; cd sealshare
curl -O https://raw.githubusercontent.com/surtic86/SealShare/main/docker-compose.example.yml
curl -O https://gitea.nonameweb.ch/noNameWEB/SealShare/raw/branch/main/docker-compose.example.yml
cp docker-compose.example.yml docker-compose.yml
<span class="comment"># Generate an app key and paste it into docker-compose.yml</span>
docker run --rm ghcr.io/surtic86/sealshare:latest php artisan key:generate --show
docker run --rm gitea.nonameweb.ch/nonameweb/sealshare:latest php artisan key:generate --show
<span class="comment"># Edit docker-compose.yml — set APP_KEY, APP_URL, and SERVER_NAME</span>
<span class="comment"># Then start:</span>
+6 -1
View File
@@ -7,7 +7,12 @@
<meta name="description" content="How the SealShare website handles visitor data: server logs at the host and Plausible analytics, nothing else.">
<meta name="theme-color" content="#faf4ff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#10062d" media="(prefers-color-scheme: dark)">
<script defer data-domain="sealshare.nonameweb.ch" src="https://plausible.io/js/script.js"></script>
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://plausible.io/js/pa-zWB4R2-rrBeLfwms3uIBT.js"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
<link rel="icon" href="img/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="img/icon.png">
<link rel="preload" href="fonts/GoogleSansFlex-Latin.woff2" as="font" type="font/woff2" crossorigin>