Compare commits

...

3 commits

Author SHA1 Message Date
Joël van de Wouw
5e52785524 Legal, marketing and UI updates 2026-08-04 10:18:10 +02:00
Joël van de Wouw
627258feaf Remove unused GitHub Actions workflows
The repo is hosted on Forgejo, not GitHub, so these CI workflows and
the dependabot config never ran.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:52:21 +02:00
Joël van de Wouw
6b6e132173 Stop tracking .claude and ignore it
Local Claude Code config (launch.json, settings.local.json) doesn't
belong in version control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:50:57 +02:00
42 changed files with 1691 additions and 364 deletions

View file

@ -1,11 +0,0 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "certified",
"runtimeExecutable": "php",
"runtimeArgs": ["/Users/joel/Development/Certified/artisan", "serve", "--port=8321"],
"port": 8321
}
]
}

View file

@ -1,7 +0,0 @@
{
"permissions": {
"allow": [
"Bash(vendor/bin/phpstan analyse *)"
]
}
}

View file

@ -4,7 +4,7 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_LOCALE=en APP_LOCALE=nl
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=en_US
@ -63,3 +63,6 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=

View file

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
groups:
github-actions:
patterns:
- "*"

View file

@ -1,51 +0,0 @@
name: linter
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
permissions:
contents: write
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.4'
- name: Add Flux Credentials Loaded From ENV
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
- name: Install Dependencies
run: |
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
npm install
- name: Run Pint
run: composer lint
# - name: Commit Changes
# uses: stefanzweifel/git-auto-commit-action@v7
# with:
# commit_message: fix code style
# commit_options: '--no-verify'
# file_pattern: |
# **/*
# !.github/workflows/*

View file

@ -1,67 +0,0 @@
name: tests
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.3', '8.4', '8.5']
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v2
coverage: xdebug
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '22'
- name: Install Node Dependencies
run: npm i
- name: Add Flux Credentials Loaded From ENV
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
- name: Install Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Copy Environment File
run: cp .env.example .env
- name: Generate Application Key
run: php artisan key:generate
- name: Build Assets
run: npm run build
- name: Run Type Analysis
run: composer types:check
- name: Run Tests
run: php artisan test

1
.gitignore vendored
View file

@ -17,6 +17,7 @@ Homestead.yaml
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
/auth.json /auth.json
/.claude
/.fleet /.fleet
/.idea /.idea
/.nova /.nova

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use App\Models\Certificate;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
class CertificatePreviewController extends Controller
{
/**
* Stream a certificate's file inline (for embedding in the view page)
* rather than forcing a download. The global scope on the model ensures
* a foreign certificate resolves to a 404.
*/
public function __invoke(Certificate $certificate): StreamedResponse
{
abort_unless(filled($certificate->file_path) && Storage::disk('local')->exists($certificate->file_path), 404);
return Storage::disk('local')->response($certificate->file_path);
}
}

View file

@ -20,4 +20,19 @@ class MarketingController extends Controller
{ {
return view('marketing.pricing'); return view('marketing.pricing');
} }
public function terms(): View
{
return view('marketing.terms');
}
public function privacy(): View
{
return view('marketing.privacy');
}
public function dpa(): View
{
return view('marketing.dpa');
}
} }

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use App\Models\Certificate;
use App\Models\Scopes\OwnedByUserScope;
use App\Rules\Turnstile;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\View\View;
/**
* Public, unauthenticated page where anyone can check whether a certificate
* number and holder last name match a genuine, currently valid certificate.
* Deliberately returns nothing beyond "valid"/"invalid" no certificate
* details are ever exposed here.
*/
class VerifyController extends Controller
{
public function show(): View
{
return view('verify.show', ['result' => Session::get('verify.result')]);
}
public function check(Request $request): RedirectResponse
{
$validated = $request->validate([
'certificate_number' => ['required', 'string', 'max:255'],
'holder_last_name' => ['required', 'string', 'max:255'],
'cf-turnstile-response' => [new Turnstile],
]);
$isValid = Certificate::query()
// Verification checks across every account's certificates, not
// just the caller's — bypass the owner scope explicitly since a
// staff member checking a certificate may be logged in too.
->withoutGlobalScope(OwnedByUserScope::class)
->whereRaw('LOWER(certificate_number) = ?', [Str::lower(trim($validated['certificate_number']))])
->whereRaw('LOWER(holder_last_name) = ?', [Str::lower(trim($validated['holder_last_name']))])
->where('expires_at', '>=', today())
->exists();
return redirect()->route('verify.show')
->with('verify.result', $isValid ? 'valid' : 'invalid');
}
}

View file

@ -20,6 +20,7 @@ use Illuminate\Support\Carbon;
* @property string $title * @property string $title
* @property string|null $issuer * @property string|null $issuer
* @property string|null $certificate_number * @property string|null $certificate_number
* @property string|null $holder_last_name
* @property Carbon|null $issued_at * @property Carbon|null $issued_at
* @property string|null $file_path * @property string|null $file_path
* @property Carbon $expires_at * @property Carbon $expires_at
@ -27,7 +28,7 @@ use Illuminate\Support\Carbon;
* @property Carbon|null $created_at * @property Carbon|null $created_at
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
*/ */
#[Fillable(['title', 'category_id', 'issuer', 'certificate_number', 'issued_at', 'file_path', 'expires_at', 'notes'])] #[Fillable(['title', 'category_id', 'issuer', 'certificate_number', 'holder_last_name', 'issued_at', 'file_path', 'expires_at', 'notes'])]
#[ScopedBy(OwnedByUserScope::class)] #[ScopedBy(OwnedByUserScope::class)]
class Certificate extends Model class Certificate extends Model
{ {

View file

@ -46,7 +46,7 @@ class User extends Authenticatable implements PasskeyUser
protected $attributes = [ protected $attributes = [
'reminder_months_before' => 1, 'reminder_months_before' => 1,
'ocr_provider' => 'none', 'ocr_provider' => 'none',
'locale' => 'en', 'locale' => 'nl',
]; ];
/** /**

41
app/Rules/Turnstile.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Request;
/**
* Verifies a Cloudflare Turnstile response token server-side. A no-op when
* services.turnstile.secret_key isn't configured, so environments without
* Turnstile keys (local, CI) aren't blocked.
*/
class Turnstile implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$secret = config('services.turnstile.secret_key');
if (blank($secret)) {
return;
}
if (blank($value)) {
$fail(__('Please complete the verification challenge.'));
return;
}
$response = Http::asForm()->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'secret' => $secret,
'response' => $value,
'remoteip' => Request::ip(),
]);
if (! $response->json('success', false)) {
$fail(__('The verification challenge failed. Please try again.'));
}
}
}

View file

@ -35,4 +35,13 @@ return [
], ],
], ],
// Cloudflare Turnstile, used to gate the public certificate verification
// form. Left unconfigured, the CAPTCHA check is skipped (see
// App\Rules\Turnstile) so local development and CI need no keys — set
// both before exposing /verify in production.
'turnstile' => [
'site_key' => env('TURNSTILE_SITE_KEY'),
'secret_key' => env('TURNSTILE_SECRET_KEY'),
],
]; ];

View file

@ -22,6 +22,7 @@ class CertificateFactory extends Factory
'title' => fake()->sentence(3), 'title' => fake()->sentence(3),
'issuer' => fake()->company(), 'issuer' => fake()->company(),
'certificate_number' => fake()->bothify('CERT-####-????'), 'certificate_number' => fake()->bothify('CERT-####-????'),
'holder_last_name' => fake()->lastName(),
'issued_at' => fake()->dateTimeBetween('-2 years', '-1 month'), 'issued_at' => fake()->dateTimeBetween('-2 years', '-1 month'),
'file_path' => null, 'file_path' => null,
'expires_at' => fake()->dateTimeBetween('+2 months', '+2 years'), 'expires_at' => fake()->dateTimeBetween('+2 months', '+2 years'),

View file

@ -30,6 +30,10 @@ class UserFactory extends Factory
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'), 'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
// Keeps existing assertions of English copy stable regardless of
// the app's production locale default; tests that care about
// Dutch rendering override this explicitly via create(['locale' => 'nl']).
'locale' => 'en',
'two_factor_secret' => null, 'two_factor_secret' => null,
'two_factor_recovery_codes' => null, 'two_factor_recovery_codes' => null,
'two_factor_confirmed_at' => null, 'two_factor_confirmed_at' => null,

View file

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('certificates', function (Blueprint $table) {
$table->string('holder_last_name')->nullable()->after('certificate_number');
$table->index(['certificate_number', 'holder_last_name']);
});
}
public function down(): void
{
Schema::table('certificates', function (Blueprint $table) {
$table->dropIndex(['certificate_number', 'holder_last_name']);
$table->dropColumn('holder_last_name');
});
}
};

395
lang/en.json Normal file
View file

@ -0,0 +1,395 @@
{
"\"AVG\"/\"GDPR\"": "\"GDPR\"",
"\"Dienst\" betekent Certified: het platform waarmee je je eigen certificaten, diploma's en vergelijkbare documenten beheert, inclusief automatische OCR-herkenning, reminders en veilig delen.": "\"Service\" means Certified: the platform on which you manage your own certificates, diplomas, and similar documents, including automatic OCR recognition, reminders, and secure sharing.",
"\"Gebruiker\" of \"je\"/\"jij\" betekent de natuurlijke persoon die een account aanmaakt en de Dienst gebruikt.": "\"User\" or \"you\" means the natural person who creates an account and uses the Service.",
"\"Inbreuk in verband met persoonsgegevens\"": "\"Personal data breach\"",
"\"Inhoud\" betekent alle certificaten, documenten, gegevens en bestanden die je naar de Dienst uploadt.": "\"Content\" means all certificates, documents, data, and files you upload to the Service.",
"\"Overeenkomst\" betekent deze Gebruiksvoorwaarden, samen met onze Privacyverklaring en, indien van toepassing, de Verwerkersovereenkomst.": "\"Agreement\" means these Terms of Service, together with our Privacy Policy and, where applicable, the Data Processing Agreement.",
"\"Persoonsgegevens\"": "\"Personal data\"",
"\"Sub-verwerker\"": "\"Sub-processor\"",
"\"Verwerker\"": "\"Processor\"",
"\"Verwerkingsverantwoordelijke\"": "\"Controller\"",
"1 maand van tevoren": "1 month ahead",
"100% Europese hosting": "100% European hosting",
"100% Europese hosting, onder Europees recht": "100% European hosting, under European law",
"2 maanden van tevoren": "2 months ahead",
"4.1 De verwerker erkent dat Certified mede bedoeld is voor het opslaan van documenten die bijzondere persoonsgegevens kunnen bevatten in de zin van artikel 9 en 10 AVG, zoals gegevens over het strafrechtelijk verleden bij een Verklaring Omtrent het Gedrag (VOG). De verwerkingsverantwoordelijke is zelf verantwoordelijk voor het hebben van een geldige rechtsgrond wanneer hij dergelijke gegevens van anderen dan zichzelf verwerkt.": "4.1 The processor acknowledges that Certified is partly intended for storing documents that may contain special category personal data within the meaning of articles 9 and 10 GDPR, such as data on criminal history in a Certificate of Conduct (VOG). The controller is responsible for having a valid legal basis when processing such data on behalf of anyone other than themselves.",
"AES-256 versleuteld": "AES-256 encrypted",
"AES-256 versleuteld delen": "AES-256 encrypted sharing",
"AES-256 versleutelde ZIP met een apart verstuurd wachtwoord": "AES-256 encrypted ZIP with a separately sent password",
"AES-256 versleutelde ZIP-export": "AES-256 encrypted ZIP export",
"AES-256 versleutelde ZIP-exports": "AES-256 encrypted ZIP exports",
"AVG / GDPR-proof": "GDPR-proof",
"Aan": "To",
"Aan de slag": "Get started",
"Aanbieder": "Provider",
"Aansprakelijkheid wordt geregeld conform artikel 82 AVG en de aansprakelijkheidsbepaling in de Gebruiksvoorwaarden. Op deze overeenkomst is Nederlands recht van toepassing; geschillen worden voorgelegd aan de bevoegde rechtbank te Arnhem.": "Liability is governed by article 82 GDPR and the liability clause in the Terms of Service. This agreement is governed by Dutch law; disputes are submitted to the competent court in Arnhem.",
"Account en authenticatie": "Account and authentication",
"Afhankelijk van hoe de verwerkingsverantwoordelijke Certified gebruikt, kunnen de volgende gegevens worden verwerkt: naam, contactgegevens, certificaatgegevens (titel, uitgever, geldigheidsdata) en het geüploade document zelf.": "Depending on how the controller uses Certified, the following data may be processed: name, contact details, certificate data (title, issuer, validity dates), and the uploaded document itself.",
"Al je certificaten op één plek.": "All your certificates in one place.",
"Al je certificaten op één plek. Nooit meer verlopen.": "All your certificates in one place. Never expired again.",
"Al je data in EU-datacenters, volledig AVG-proof.": "All your data in EU data centers, fully GDPR-proof.",
"Al je data staat in Europese datacenters, onder Europees recht. Geen Amerikaanse cloud, geen CLOUD Act, volledig AVG/GDPR-compliant.": "All your data is stored in European data centers, under European law. No American cloud, no CLOUD Act, fully GDPR compliant.",
"Algemene voorwaarden": "Terms of Service",
"Alle applicatie- en documentdata staat in Europese datacenters. We gebruiken bewust geen Amerikaanse hyperscalers: je data valt daardoor niet onder de Amerikaanse CLOUD Act en blijft volledig binnen de reikwijdte van de AVG/GDPR. Back-ups worden versleuteld opgeslagen, eveneens binnen de EU.": "All application and document data is stored in European data centers. We deliberately avoid American hyperscalers: your data therefore does not fall under the American CLOUD Act and remains fully within the scope of the GDPR. Backups are stored encrypted, also within the EU.",
"Alle prijzen zijn per jaar en exclusief btw. Start gratis — upgraden kan altijd, opzeggen ook.": "All prices are per year and exclude VAT. Start for free — you can upgrade or cancel at any time.",
"Alle rechten voorbehouden.": "All rights reserved.",
"Alleen jij, vanuit je eigen account. De scheiding tussen accounts wordt op databaseniveau afgedwongen. Onze eigen medewerkers hebben geen toegang tot documentinhoud, behalve wanneer jij daar bij een supportverzoek expliciet toestemming voor geeft.": "Only you, from your own account. The separation between accounts is enforced at the database level. Our own staff have no access to document content, except when you explicitly grant permission for a support request.",
"Alles over facturering, opzeggen en overstappen.": "Everything about billing, cancelling, and switching plans.",
"Applicatiehosting, bestandsopslag en databases": "Application hosting, file storage, and databases",
"Artikel 1 — Definities": "Article 1 — Definitions",
"Artikel 1 — Partijen en definities": "Article 1 — Parties and definitions",
"Artikel 10 — Opzeggen en verwijdering van je data": "Article 10 — Cancelling and deletion of your data",
"Artikel 10 — Rechten van betrokkenen": "Article 10 — Rights of data subjects",
"Artikel 11 — Verwijdering en teruggave": "Article 11 — Deletion and return",
"Artikel 11 — Wijzigingen van deze voorwaarden": "Article 11 — Changes to these terms",
"Artikel 12 — Auditrechten": "Article 12 — Audit rights",
"Artikel 12 — Toepasselijk recht en bevoegde rechter": "Article 12 — Governing law and jurisdiction",
"Artikel 13 — Aansprakelijkheid en toepasselijk recht": "Article 13 — Liability and governing law",
"Artikel 2 — Acceptatie en geschiktheid": "Article 2 — Acceptance and eligibility",
"Artikel 2 — Onderwerp, aard en doel": "Article 2 — Subject matter, nature, and purpose",
"Artikel 3 — Looptijd": "Article 3 — Duration",
"Artikel 3 — Toegang en accountverplichtingen": "Article 3 — Access and account obligations",
"Artikel 4 — Aanvaardbaar gebruik": "Article 4 — Acceptable use",
"Artikel 4 — Persoonsgegevens en betrokkenen": "Article 4 — Personal data and data subjects",
"Artikel 5 — Abonnementen en betaling": "Article 5 — Subscriptions and payment",
"Artikel 5 — Verplichtingen van de verwerker": "Article 5 — Obligations of the processor",
"Artikel 6 — Intellectueel eigendom": "Article 6 — Intellectual property",
"Artikel 6 — Sub-verwerkers": "Article 6 — Sub-processors",
"Artikel 7 — Beschikbaarheid en wijzigingen van de Dienst": "Article 7 — Availability and changes to the Service",
"Artikel 7 — Internationale doorgiften": "Article 7 — International transfers",
"Artikel 8 — Beveiligingsmaatregelen": "Article 8 — Security measures",
"Artikel 8 — Gegevens, privacy en OCR-verwerking": "Article 8 — Data, privacy, and OCR processing",
"Artikel 9 — Aansprakelijkheid": "Article 9 — Liability",
"Artikel 9 — Meldplicht datalekken": "Article 9 — Data breach notification",
"Automatische OCR": "Automatic OCR",
"Automatische OCR-scan": "Automatic OCR scan",
"Automatische tekstherkenning (OCR) vindt alleen plaats als jij dit zelf inschakelt, via een door jou gekozen Europese provider. Je kunt OCR ook uitschakelen en velden handmatig invullen.": "Automatic text recognition (OCR) only takes place if you enable it yourself, via a European provider of your choice. You can also disable OCR and fill in fields manually.",
"Bckstage exploiteert Certified, een persoonlijke certificatenmanager waarmee je zelf je certificaten, diploma's en documenten zoals een VOG bijhoudt. Deze verklaring legt uit welke persoonsgegevens we verzamelen, waarom, hoe lang we ze bewaren en wat je rechten zijn onder de AVG/GDPR.": "Bckstage operates Certified, a personal certificate manager that lets you keep track of your own certificates, diplomas, and documents such as a Certificate of Conduct (VOG). This policy explains what personal data we collect, why, how long we keep it, and what rights you have under the GDPR.",
"Bckstage is verwerkingsverantwoordelijke voor alle persoonsgegevens die worden verwerkt om je Certified te leveren: je accountgegevens en de certificaatgegevens en documenten die je zelf uploadt.": "Bckstage is the controller for all personal data processed to provide you with Certified: your account data and the certificate data and documents you upload yourself.",
"Bckstage, die persoonsgegevens verwerkt namens de verwerkingsverantwoordelijke.": "Bckstage, which processes personal data on behalf of the controller.",
"Bekijk de prijzen": "View pricing",
"Bekijk de verwerkersovereenkomst": "View the Data Processing Agreement",
"Betaalde pakketten worden één keer per jaar gefactureerd: €12,50 per jaar voor Starter, €45 per jaar voor Onbeperkt, beide exclusief btw. Je ontvangt een factuur die je direct in je administratie kunt verwerken.": "Paid plans are billed once a year: €12.50 per year for Starter, €45 per year for Unlimited, both excluding VAT. You receive an invoice you can process directly in your own bookkeeping.",
"Beveiliging": "Security",
"Beveiligingslogboeken": "Security logs",
"Bezorging van reminders en accountmeldingen": "Delivery of reminders and account notifications",
"Bij beëindiging van het abonnement verwijdert de verwerker, naar keuze van de verwerkingsverantwoordelijke, alle persoonsgegevens of geeft deze terug in een machineleesbaar formaat. Zonder tijdige instructie verwijdert de verwerker alle gegevens binnen 30 dagen na beëindiging, inclusief back-ups binnen maximaal 90 dagen vanwege back-uprotatieschema's.": "Upon termination of the subscription, the processor will, at the controller's choice, delete all personal data or return it in a machine-readable format. Without timely instructions, the processor deletes all data within 30 days of termination, including backups within a maximum of 90 days due to backup rotation schedules.",
"Bijlage blijft voor altijd in inboxen en op mailservers staan": "The attachment stays in inboxes and on mail servers forever",
"Bijstand": "Assistance",
"Binnen tien minuten staan je eerste certificaten in Certified — inclusief automatische reminders. Start gratis met maximaal 5 certificaten.": "Within ten minutes your first certificates are in Certified — including automatic reminders. Start for free with up to 5 certificates.",
"Categorieën": "Categories",
"Certificaat": "Certificate",
"Certificaat herkend en ingevuld — klaar om op te slaan": "Certificate recognized and filled in — ready to save",
"Certificaatgegevens": "Certificate data",
"Certificaten": "Certificates",
"Certificaten die je deelt worden verpakt in een AES-256 versleutelde ZIP. Links zijn ondertekend en verlopen automatisch na 48 uur.": "Certificates you share are packaged into an AES-256 encrypted ZIP. Links are signed and automatically expire after 48 hours.",
"Certified bewaakt de geldigheid van al je certificaten. Automatische OCR, slimme reminders en AVG-proof delen — 100% Europese hosting.": "Certified watches over the validity of all your certificates. Automatic OCR, smart reminders, and GDPR-proof sharing — 100% European hosting.",
"Certified bewaart documenten met bijzondere persoonsgegevens — je VOG en andere gevoelige diploma's en certificaten. Op deze pagina lees je precies hoe we die gegevens beschermen.": "Certified stores documents containing special category personal data — your Certificate of Conduct (VOG) and other sensitive diplomas and certificates. This page explains exactly how we protect that data.",
"Certified controleert dagelijks alle vervaldatums en stuurt automatisch reminders — ruim op tijd om een certificaat te verlengen. Verloopt er toch iets? Dan krijg je direct een urgente melding, zodat het nooit onopgemerkt blijft.": "Certified checks all expiry dates daily and automatically sends reminders — well in time to renew a certificate. Does something expire anyway? You get an urgent notification right away, so it never goes unnoticed.",
"Certified gebruikt uitsluitend strikt noodzakelijke cookies: een sessiecookie om je ingelogd te houden, een CSRF-tokencookie ter beveiliging, en een voorkeurscookie voor je taal en weergave (licht/donker). We gebruiken geen reclamecookies, trackingpixels of analyses van derden. Er is geen cookiebanner nodig omdat we uitsluitend strikt noodzakelijke cookies plaatsen.": "Certified uses only strictly necessary cookies: a session cookie to keep you signed in, a CSRF token cookie for security, and a preference cookie for your language and appearance (light/dark). We do not use advertising cookies, tracking pixels, or third-party analytics. No cookie banner is required because we only place strictly necessary cookies.",
"Certified herinnert je opnieuw zolang het niet is verlengd.": "Certified reminds you again as long as it hasn't been renewed.",
"Certified is bedoeld voor het beheren van je eigen certificaten en documenten door jou als individu — niet voor het op grote schaal beheren van certificaten van andere personen namens een bedrijf of organisatie.": "Certified is intended for managing your own certificates and documents as an individual — not for managing certificates of other people on a large scale on behalf of a company or organization.",
"Certified is een hulpmiddel om je certificaten te overzien en je te herinneren aan vervaldatums — geen garantie dat je nooit een deadline mist. De Dienst wordt geleverd \"zoals het is\".": "Certified is a tool to help you keep an overview of your certificates and remind you of expiry dates — not a guarantee that you'll never miss a deadline. The Service is provided \"as is\".",
"Certified is gebouwd voor persoonlijk gebruik: jij beheert je eigen VOG, diploma's en certificaten. In dat geval ben je zelf geen verwerkingsverantwoordelijke in de zin van de AVG en heb je deze overeenkomst niet nodig.": "Certified is built for personal use: you manage your own Certificate of Conduct (VOG), diplomas, and certificates. In that case, you are not a controller within the meaning of the GDPR and you don't need this agreement.",
"Certified is niet bedoeld voor gebruik door personen jonger dan 16 jaar. Vermoed je dat we onbedoeld gegevens van een minderjarige hebben verzameld, neem dan contact met ons op zodat we deze kunnen verwijderen.": "Certified is not intended for use by people under the age of 16. If you suspect we have unintentionally collected data from a minor, please contact us so we can delete it.",
"Certified kent een gratis pakket en de betaalde abonnementen Starter en Onbeperkt, zoals beschreven op onze prijzenpagina. Betaalde abonnementen worden jaarlijks vooraf gefactureerd, exclusief btw.": "Certified has a free plan and the paid subscriptions Starter and Unlimited, as described on our pricing page. Paid subscriptions are billed annually in advance, excluding VAT.",
"Certified scant je VOG, diploma's en certificaten automatisch in, bewaakt elke vervaldatum en deelt ze alleen versleuteld — zonder Excel-lijstjes, losse mapjes of kwetsbare e-mailbijlagen.": "Certified automatically scans your Certificate of Conduct (VOG), diplomas, and certificates, watches every expiry date, and only shares them encrypted — no spreadsheets, loose folders, or vulnerable email attachments.",
"Certified — home": "Certified — home",
"Contact": "Contact",
"Cookies": "Cookies",
"Cryptografisch ondertekende deel-URL's": "Cryptographically signed sharing URLs",
"Data-isolatie afgedwongen op databaseniveau": "Data isolation enforced at the database level",
"Datacenters in de EU, data verlaat Europa nooit": "Data centers in the EU, data never leaves Europe",
"De Dienst gebruikt uitsluitend strikt noodzakelijke functionele cookies — geen reclame- of trackingcookies.": "The Service uses only strictly necessary functional cookies — no advertising or tracking cookies.",
"De gebruiksvoorwaarden voor het gebruik van Certified.": "The terms of service for using Certified.",
"De klant": "The customer",
"De natuurlijke persoon die een account aanmaakt bij Certified om zijn eigen certificaten, diploma's en documenten te beheren.": "The natural person who creates an account with Certified to manage their own certificates, diplomas, and documents.",
"De standaard verwerkersovereenkomst (DPA) van Certified, beschikbaar voor zzp'ers en eenmanszaken die er één nodig hebben.": "Certified's standard Data Processing Agreement (DPA), available for freelancers and sole proprietors who need one.",
"De veilige plek waar je zelf al je certificaten, diploma's en VOG's beheert. Gebouwd en gehost in Europa.": "The secure place where you manage all your own certificates, diplomas, and Certificates of Conduct. Built and hosted in Europe.",
"De verwerker geeft geen persoonsgegevens door aan derde landen buiten de EER, behalve aan Resend (Verenigde Staten) voor het bezorgen van reminders, op basis van passende waarborgen conform Hoofdstuk V AVG. Alle overige sub-verwerkers zijn gevestigd binnen de EU.": "The processor does not transfer personal data to third countries outside the EEA, except to Resend (United States) for delivering reminders, based on appropriate safeguards under Chapter V GDPR. All other sub-processors are located within the EU.",
"De verwerker handelt uitsluitend op gedocumenteerde instructies van de verwerkingsverantwoordelijke, tenzij Unierecht of nationale wetgeving anders vereist.": "The processor acts only on documented instructions from the controller, unless required otherwise by Union or national law.",
"De verwerker helpt de verwerkingsverantwoordelijke bij het beantwoorden van verzoeken van betrokkenen (inzage, rectificatie, verwijdering, beperking, overdraagbaarheid, bezwaar) en stuurt verzoeken die rechtstreeks bij de verwerker binnenkomen binnen 5 werkdagen door.": "The processor helps the controller respond to data subject requests (access, rectification, erasure, restriction, portability, objection) and forwards any request it receives directly within 5 business days.",
"De verwerker stelt de verwerkingsverantwoordelijke zonder onredelijke vertraging, en in elk geval binnen 72 uur nadat hij kennis heeft gekregen van een inbreuk in verband met persoonsgegevens, hiervan op de hoogte — met een beschrijving van de aard, de waarschijnlijke gevolgen en de genomen maatregelen.": "The processor notifies the controller without undue delay, and in any case within 72 hours of becoming aware of a personal data breach — with a description of the nature, likely consequences, and measures taken.",
"De verwerker verstrekt de informatie die redelijkerwijs nodig is om naleving van artikel 28 AVG aan te tonen. Auditverzoeken worden schriftelijk ingediend met ten minste 30 dagen vooraankondiging en mogen de beveiliging van gegevens van andere klanten niet in gevaar brengen.": "The processor provides the information reasonably necessary to demonstrate compliance with article 28 GDPR. Audit requests must be submitted in writing with at least 30 days' notice and may not jeopardize the security of other customers' data.",
"De verwerker verwerkt persoonsgegevens niet voor eigen doeleinden, reclame, profilering of enig ander doel dan het leveren en verbeteren van Certified.": "The processor does not process personal data for its own purposes, advertising, profiling, or any purpose other than providing and improving Certified.",
"De verwerker verwerkt persoonsgegevens uitsluitend om Certified te leveren aan de verwerkingsverantwoordelijke: opslag, OCR-herkenning, het bewaken van vervaldatums en het veilig delen van certificaten.": "The processor processes personal data solely to provide Certified to the controller: storage, OCR recognition, monitoring expiry dates, and securely sharing certificates.",
"De vragen die we het vaakst krijgen over privacy en gegevensbescherming.": "The questions we get asked most often about privacy and data protection.",
"De zzp'er of eenmanszaak die Certified gebruikt en zelf certificaatgegevens van anderen verwerkt.": "The freelancer or sole proprietor who uses Certified and processes certificate data of others themselves.",
"Delen met": "Sharing with",
"Derden en internationale doorgiften": "Third parties and international transfers",
"Deze overeenkomst blijft van kracht gedurende de looptijd van het abonnement en eindigt automatisch bij beëindiging daarvan, onder voorbehoud van artikel 11 (Verwijdering en teruggave).": "This agreement remains in effect for the duration of the subscription and automatically ends upon its termination, subject to article 11 (Deletion and return).",
"Deze overeenkomst wordt geacht te zijn aanvaard bij eerste gebruik van Certified in de context van je eigen bedrijfsvoering. Voor een getekende kopie voor je eigen archief mail je ons via": "This agreement is deemed accepted upon first use of Certified in the context of your own business. For a signed copy for your own records, email us at",
"Diploma's & getuigschriften": "Diplomas & certificates of completion",
"Direct een urgente melding in je inbox": "An urgent notification straight to your inbox",
"Document scannen…": "Scanning document…",
"Documenten uploaden die niet van jou zijn, zonder toestemming van de rechthebbende": "Uploading documents that are not yours without permission from the rights holder",
"Documenten zoals een VOG bevatten bijzondere persoonsgegevens. Daarom is Certified vanaf de eerste regel code gebouwd op privacy-by-design — en blijft je data binnen de EU.": "Documents such as a Certificate of Conduct contain special category personal data. That's why Certified was built on privacy-by-design from the very first line of code — and your data stays within the EU.",
"Doel": "Purpose",
"Door deze overeenkomst aan te gaan geeft de verwerkingsverantwoordelijke algemene toestemming aan de verwerker om de volgende sub-verwerkers in te schakelen:": "By entering into this agreement, the controller gives general authorization to the processor to engage the following sub-processors:",
"Door een account aan te maken of de Dienst te gebruiken, ga je akkoord met deze Overeenkomst. Ga je hier niet mee akkoord, dan mag je de Dienst niet gebruiken.": "By creating an account or using the Service, you agree to this Agreement. If you do not agree, you may not use the Service.",
"Doorverkoop of sublicentiëring van toegang tot de Dienst zonder onze voorafgaande schriftelijke toestemming": "Reselling or sublicensing access to the Service without our prior written consent",
"Downgraden of opzeggen van een betaald abonnement gaat in op de eerstvolgende verlengingsdatum. Reeds betaalde bedragen worden niet naar rato terugbetaald.": "Downgrading or cancelling a paid subscription takes effect on the next renewal date. Amounts already paid are not refunded pro rata.",
"Drie pijlers die samen je hele certificatenbeheer afdekken — zonder handwerk.": "Three pillars that together cover your entire certificate management — without manual work.",
"E-mail op het moment dat jij instelt, plus urgente meldingen.": "Email at the time you set, plus urgent notifications.",
"E-mailbijlage": "Email attachment",
"EHBO & BHV": "First aid & emergency response",
"EU (Nederland)": "EU (Netherlands)",
"Een volledig overzicht van onze beveiligingsmaatregelen vind je op onze security-pagina.": "A full overview of our security measures can be found on our security page.",
"Eerlijke prijzen voor je eigen certificaatbeheer. Start gratis met maximaal 5 certificaten — inclusief OCR, slimme reminders en AVG-proof delen.": "Fair pricing for managing your own certificates. Start for free with up to 5 certificates — including OCR, smart reminders, and GDPR-proof sharing.",
"Eerlijke prijzen, geen verrassingen": "Fair pricing, no surprises",
"Eigen infrastructuur": "Own infrastructure",
"Elk gebruik dat in strijd is met toepasselijk recht of rechten van derden": "Any use that violates applicable law or the rights of third parties",
"Europese OCR-providers": "European OCR providers",
"Europese hosting": "European hosting",
"Europese privacy & security": "European privacy & security",
"Eén vast bedrag, jaarlijks gefactureerd": "One fixed price, billed annually",
"Eén verlopen certificaat kost je al snel meer dan een jaar Certified. Start gratis en betaal pas als je meer certificaten beheert.": "One expired certificate can easily cost you more than a year of Certified. Start for free and only pay once you manage more certificates.",
"Facturering (betaalde abonnementen)": "Billing (paid subscriptions)",
"Facturering van betaalde abonnementen": "Billing of paid subscriptions",
"Features": "Features",
"Frankrijk (EU)": "France (EU)",
"Geautomatiseerd scrapen, crawlen of op grote schaal gegevens onttrekken aan de Dienst": "Automated scraping, crawling, or large-scale data extraction from the Service",
"Gebruik je Certified puur voor je eigen certificaten? Dan heb je geen verwerkersovereenkomst nodig — je bent geen verwerkingsverantwoordelijke in de zin van de AVG. Werk je als zzp'er of eenmanszaak en wil je er toch één voor je eigen administratie, dan kun je die downloaden op onze DPA-pagina.": "Do you use Certified purely for your own certificates? Then you don't need a Data Processing Agreement — you are not a controller within the meaning of the GDPR. If you work as a freelancer or sole proprietor and want one for your own records anyway, you can download it on our DPA page.",
"Gebruik je Certified voor je eigen certificaten, dan heb je geen verwerkersovereenkomst nodig — je bent zelf geen verwerkingsverantwoordelijke onder de AVG. Werk je als zzp'er of eenmanszaak en wil je er voor je eigen administratie toch één, dan staat de standaard verwerkersovereenkomst voor je klaar.": "If you use Certified for your own certificates, you don't need a Data Processing Agreement — you are not a controller under the GDPR. If you work as a freelancer or sole proprietor and want one for your own records anyway, the standard agreement is ready for you.",
"Gebruiker": "User",
"Gedeelde documenten alleen bereikbaar via cryptografisch ondertekende, automatisch verlopende links": "Shared documents only accessible via cryptographically signed links that expire automatically",
"Geen Amerikaanse cloudproviders in de keten": "No American cloud providers in the chain",
"Geen controle: doorsturen, kopiëren en lekken kan ongemerkt": "No control: forwarding, copying, and leaks can go unnoticed",
"Geen creditcard nodig": "No credit card required",
"Geldig": "Valid",
"Gemaakt in Nederland": "Made in the Netherlands",
"Geschillen worden voorgelegd aan de bevoegde rechtbank te Arnhem, Nederland, tenzij dwingend consumentenrecht in jouw woonland anders bepaalt.": "Disputes are submitted to the competent court in Arnhem, the Netherlands, unless mandatory consumer law in your country of residence provides otherwise.",
"Gevoelige gegevens verdienen Europese waarborgen": "Sensitive data deserves European safeguards",
"Gratis": "Free",
"Gratis tot 5 certificaten": "Free up to 5 certificates",
"Heb ik een verwerkersovereenkomst nodig bij een betaald pakket?": "Do I need a Data Processing Agreement with a paid plan?",
"Heb ik een verwerkersovereenkomst nodig?": "Do I need a Data Processing Agreement?",
"Heb je deze wel nodig?": "Do you actually need this?",
"Herinnering": "Reminder",
"Herkent titel, uitgever en vervaldatum in seconden": "Recognizes title, issuer, and expiry date in seconds",
"Het document wordt via een beveiligde verbinding naar de door jou gekozen Europese provider (Klippa of Mistral AI) gestuurd, die er tekst uit herkent en het resultaat teruggeeft. Wil je dit niet, dan zet je OCR uit en vul je de velden handmatig in.": "The document is sent over a secure connection to the European provider you chose (Klippa or Mistral AI), which recognizes text in it and returns the result. If you don't want this, turn off OCR and fill in the fields manually.",
"Het volgende is niet toegestaan:": "The following is not allowed:",
"Hoe Certified je certificaten beschermt: 100% Europese hosting, AES-256 versleuteld delen en keuze uit Europese OCR-providers. Volledig AVG/GDPR-compliant.": "How Certified protects your certificates: 100% European hosting, AES-256 encrypted sharing, and a choice of European OCR providers. Fully GDPR compliant.",
"Hoe Certified omgaat met je persoonsgegevens: welke gegevens we verzamelen, waarom, en welke rechten je hebt.": "How Certified handles your personal data: what data we collect, why, and what rights you have.",
"Hoe lang blijft een gedeelde link geldig?": "How long does a shared link stay valid?",
"Hoe we met je persoonsgegevens omgaan staat beschreven in onze Privacyverklaring, die integraal onderdeel uitmaakt van deze Overeenkomst.": "How we handle your personal data is described in our Privacy Policy, which forms an integral part of this Agreement.",
"Hoe werkt de automatische OCR-scan?": "How does the automatic OCR scan work?",
"Hoe werkt de facturering?": "How does billing work?",
"Hoofdnavigatie": "Main navigation",
"Hosting & jurisdictie": "Hosting & jurisdiction",
"IP-adres, browser- en apparaatinformatie en inloggebeurtenissen, gebruikt om misbruik te detecteren en beveiligingsincidenten te onderzoeken.": "IP address, browser and device information, and login events, used to detect abuse and investigate security incidents.",
"Inbegrepen bij elk pakket": "Included in every plan",
"Instellingen": "Settings",
"Is Certified geschikt voor gevoelige documenten, zoals een VOG?": "Is Certified suitable for sensitive documents, such as a Certificate of Conduct?",
"Is het Gratis-pakket echt gratis?": "Is the Free plan really free?",
"Ja, daar is Certified juist voor gebouwd. Je documenten staan versleuteld op Europese servers, worden nooit als open bijlage gedeeld en zijn alleen toegankelijk met jouw account. Delen met anderen gebeurt via een tijdelijke ondertekende link of een AES-256 versleutelde ZIP.": "Yes, that's exactly what Certified is built for. Your documents are stored encrypted on European servers, are never shared as an open attachment, and are only accessible with your account. Sharing with others happens via a temporary signed link or an AES-256 encrypted ZIP.",
"Ja, voor altijd. Je beheert er tot 5 certificaten mee, inclusief OCR-scan, reminders en veilig delen. Geen creditcard, geen verborgen kosten — pas als je meer dan 5 certificaten wilt bewaken, stap je over op een betaald pakket.": "Yes, forever. You can manage up to 5 certificates with it, including OCR scanning, reminders, and secure sharing. No credit card, no hidden costs — you only switch to a paid plan once you want to track more than 5 certificates.",
"Ja. Je kunt op elk moment al je certificaten en metadata exporteren. Zeg je op, dan verwijderen we je data — inclusief back-ups — binnen 30 dagen definitief, conform de AVG.": "Yes. You can export all your certificates and metadata at any time. If you cancel, we permanently delete your data — including backups — within 30 days, in accordance with the GDPR.",
"Je BHV-diploma verloopt over 2 maanden": "Your emergency response certificate expires in 2 months",
"Je VOG of diploma als onbeveiligde bijlage naar een nieuwe werkgever of instantie mailen is vragen om problemen. Certified deelt je documenten alleen versleuteld en tijdelijk.": "Emailing your Certificate of Conduct or diploma as an unsecured attachment to a new employer or authority is asking for trouble. Certified only shares your documents encrypted and temporarily.",
"Je account is persoonlijk; het delen van je inloggegevens met anderen is niet toegestaan.": "Your account is personal; sharing your login details with others is not allowed.",
"Je bent verantwoordelijk voor het geheimhouden van je wachtwoord of passkey en meldt vermoed onbevoegd gebruik direct aan ons.": "You are responsible for keeping your password or passkey confidential and for reporting any suspected unauthorized use to us immediately.",
"Je bent zelf verantwoordelijk voor het geven van juiste en actuele gegevens bij registratie, en voor het up-to-date houden daarvan.": "You are responsible for providing accurate and up-to-date information when registering, and for keeping it up to date.",
"Je certificaten en accountgegevens worden opgeslagen in datacenters binnen de Europese Unie. We gebruiken geen Amerikaanse cloudproviders voor hosting, waardoor je gegevens niet onder de Amerikaanse CLOUD Act vallen.": "Your certificates and account data are stored in data centers within the European Union. We do not use American cloud providers for hosting, so your data does not fall under the American CLOUD Act.",
"Je documenten worden versleuteld opgeslagen en al het verkeer loopt via TLS. Deel je een certificaat met iemand anders, dan gebeurt dat via een AES-256 versleutelde ZIP met een apart verstuurd wachtwoord, of via een ondertekende link die na 48 uur automatisch onbruikbaar wordt. API-sleutels van OCR-providers slaan we versleuteld op.": "Your documents are stored encrypted and all traffic runs over TLS. If you share a certificate with someone else, this happens via an AES-256 encrypted ZIP with a separately sent password, or via a signed link that automatically becomes unusable after 48 hours. We store OCR provider API keys encrypted.",
"Je kunt je account op elk moment opzeggen vanuit je instellingen. Je kunt daarvoor al je certificaten en gegevens exporteren. Na opzegging verwijderen we je gegevens, inclusief back-ups, definitief binnen 30 dagen. Wij kunnen je account opschorten of beëindigen bij een wezenlijke schending van artikel 4.": "You can cancel your account at any time from your settings. Before doing so, you can export all your certificates and data. After cancellation, we permanently delete your data, including backups, within 30 days. We may suspend or terminate your account for a material breach of article 4.",
"Je uploadt een PDF of foto van je certificaat. Onze OCR-engine herkent automatisch de titel, uitgever en vervaldatum en vult die voor je in. Jij controleert en slaat op — dat is alles. Je kiest zelf welke Europese provider (Klippa of Mistral AI) de verwerking doet.": "You upload a PDF or photo of your certificate. Our OCR engine automatically recognizes the title, issuer, and expiry date and fills them in for you. You check and save — that's it. You choose which European provider (Klippa or Mistral AI) does the processing.",
"Je verklaart dat je ten minste 16 jaar oud bent.": "You declare that you are at least 16 years old.",
"Jij": "You",
"Jij behoudt alle rechten op je eigen Inhoud. Je geeft ons een beperkte licentie om je Inhoud op te slaan, te verwerken en te verzenden, uitsluitend voor zover nodig om de Dienst aan jou te leveren.": "You retain all rights to your own Content. You grant us a limited license to store, process, and transmit your Content, solely to the extent necessary to provide the Service to you.",
"Jij bepaalt zelf hoeveel maanden van tevoren je gewaarschuwd wilt worden.": "You decide for yourself how many months in advance you want to be warned.",
"Jij kiest welke Europese partij je documenten leest": "You choose which European party reads your documents",
"Jij ziet uitsluitend je eigen certificaten — afgedwongen op databaseniveau, niet alleen in de interface. Inloggen kan met wachtwoord of passkeys, en documenten die je deelt zijn alleen bereikbaar via cryptografisch ondertekende URL's die niet te raden of te vervalsen zijn.": "You only ever see your own certificates — enforced at the database level, not just in the interface. You can sign in with a password or passkeys, and documents you share are only reachable via cryptographically signed URLs that cannot be guessed or forged.",
"Jouw rechten": "Your rights",
"Juridisch": "Legal",
"Kan ik Certified eerst vrijblijvend proberen?": "Can I try Certified first without obligation?",
"Kan ik al mijn data exporteren of laten verwijderen?": "Can I export or delete all my data?",
"Kan ik tussentijds upgraden, downgraden of opzeggen?": "Can I upgrade, downgrade, or cancel in the middle of the term?",
"Keuze uit Europese providers: Klippa of Mistral AI": "Choice of European providers: Klippa or Mistral AI",
"Kies zelf wie je documenten leest: Klippa (Nederland) of Mistral AI (Frankrijk). Je documenten verlaten Europa nooit — jij houdt de regie.": "Choose for yourself who reads your documents: Klippa (Netherlands) or Mistral AI (France). Your documents never leave Europe — you stay in control.",
"Klippa: Nederlandse document-AI specialist": "Klippa: Dutch document AI specialist",
"Lees alles over security & privacy": "Read all about security & privacy",
"Locatie": "Location",
"Login": "Login",
"Malware, virussen of schadelijke code uploaden of verspreiden via de Dienst": "Uploading or spreading malware, viruses, or harmful code via the Service",
"Maximaal 20 certificaten": "Up to 20 certificates",
"Maximaal 48 uur. Daarna is de ondertekende URL cryptografisch ongeldig en kan niemand het document meer openen — ook niet met de oude link. Versleutelde ZIP-exports blijven beveiligd met AES-256 en het apart verstuurde wachtwoord.": "A maximum of 48 hours. After that, the signed URL is cryptographically invalid and no one can open the document anymore — not even with the old link. Encrypted ZIP exports remain protected with AES-256 and the separately sent password.",
"Maximaal 5 certificaten": "Up to 5 certificates",
"Meest gekozen": "Most popular",
"Meestal niet — gebruik je Certified voor jezelf, dan ben je geen verwerkingsverantwoordelijke. Wil je er als zzp'er of eenmanszaak toch één voor je administratie, dan staat de standaard verwerkersovereenkomst gratis voor je klaar op de DPA-pagina.": "Usually not — if you use Certified for yourself, you are not a controller. If you're a freelancer or sole proprietor and want one for your own records anyway, the standard Data Processing Agreement is available for free on our DPA page.",
"Menu openen": "Open menu",
"Mijn VOG — zie bijlage": "My Certificate of Conduct — see attachment",
"Minderjarigen": "Minors",
"Mistral AI: Frans AI-bedrijf, EU-gehost": "Mistral AI: French AI company, EU-hosted",
"Naam, e-mailadres, gehasht wachtwoord of passkey, taalvoorkeur en sessiegegevens.": "Name, email address, hashed password or passkey, language preference, and session data.",
"Naam, factuuradres en e-mailadres voor facturering. Betalingsgegevens worden niet door Bckstage zelf opgeslagen.": "Name, billing address, and email address for invoicing. Payment details are not stored by Bckstage itself.",
"Nederland": "The Netherlands",
"Nederland (EU)": "Netherlands (EU)",
"Nederlands recht, EU-hosting": "Dutch law, EU hosting",
"Nee. Certified draait uitsluitend op Europese infrastructuur van Europese leveranciers. Er is geen Amerikaanse partij in de hostingketen, dus de CLOUD Act is niet van toepassing op jouw data.": "No. Certified runs exclusively on European infrastructure from European providers. There is no American party in the hosting chain, so the CLOUD Act does not apply to your data.",
"Nee. OCR-scans zitten bij elk pakket inbegrepen — ook bij Gratis. Je betaalt nooit per scan.": "No. OCR scans are included in every plan — even the Free one. You never pay per scan.",
"Neem contact met ons op via": "Contact us at",
"Niets ergs — je data blijft gewoon staan. We sturen een melding en je kunt eenvoudig upgraden naar het volgende pakket. We verwijderen nooit certificaten omdat je over een limiet heen bent.": "Nothing bad — your data simply stays put. We send a notification and you can easily upgrade to the next plan. We never delete certificates because you've gone over a limit.",
"Niets in deze Overeenkomst beperkt aansprakelijkheid voor overlijden of persoonlijk letsel door onze nalatigheid, fraude, of andere aansprakelijkheid die niet kan worden uitgesloten onder Nederlands recht.": "Nothing in this Agreement limits liability for death or personal injury caused by our negligence, fraud, or any other liability that cannot be excluded under Dutch law.",
"Nog geen nieuw certificaat geüpload": "No new certificate uploaded yet",
"Nooit meer per ongeluk verlopen.": "Never accidentally expired again.",
"OCR volledig uitschakelbaar per account": "OCR fully switchable off per account",
"OCR-herkenning — alleen als jij dit inschakelt": "OCR recognition — only if you enable it",
"OCR-herkenning — alleen indien geactiveerd": "OCR recognition — only if activated",
"OCR-verwerking": "OCR processing",
"OCR-verwerking (optioneel)": "OCR processing (optional)",
"Onbeperkt": "Unlimited",
"Onbeperkt certificaten": "Unlimited certificates",
"Onbeveiligd verstuurd — een onnodig risico op misbruik van je gegevens": "Sent unsecured — an unnecessary risk of misuse of your data",
"Onder de AVG heb je recht op inzage, rectificatie, verwijdering, beperking van de verwerking, gegevensoverdraagbaarheid en bezwaar. Je kunt je certificaten en gegevens grotendeels zelf inzien en exporteren vanuit je account; voor andere verzoeken mail je ons. We reageren binnen één maand. Je hebt ook het recht een klacht in te dienen bij de Autoriteit Persoonsgegevens.": "Under the GDPR you have the right to access, rectification, erasure, restriction of processing, data portability, and objection. You can view and export most of your certificates and data yourself from your account; for other requests, email us. We respond within one month. You also have the right to lodge a complaint with the Dutch Data Protection Authority.",
"Ondersteuning voor passkeys (phishing-bestendig inloggen)": "Passkey support (phishing-resistant sign-in)",
"Ondersteuning voor phishing-bestendige authenticatie via passkeys": "Support for phishing-resistant authentication via passkeys",
"Ondertekende deel-links met een maximale geldigheid van 48 uur": "Signed sharing links valid for a maximum of 48 hours",
"Ondertekende links (48u) en AES-256 versleutelde ZIP's.": "Signed links (48h) and AES-256 encrypted ZIPs.",
"Ondertekening": "Signing",
"Ontdek hoe het werkt": "See how it works",
"Op deze Overeenkomst is Nederlands recht van toepassing.": "This Agreement is governed by Dutch law.",
"Op tijd gewaarschuwd, elke keer weer": "Warned in time, every single time",
"Opzeggen wanneer je wilt": "Cancel whenever you want",
"Overzicht hoeft niet duur te zijn": "A clear overview doesn't have to be expensive",
"Partij": "Party",
"Pricing": "Pricing",
"Prijzen": "Pricing",
"Prioriteit support": "Priority support",
"Privacy-by-design, gebouwd in Europa": "Privacy-by-design, built in Europe",
"Privacyverklaring": "Privacy Policy",
"Privacyvriendelijk: geen kopieën die ergens blijven rondslingeren": "Privacy-friendly: no copies left lying around anywhere",
"Probeer de demo-scan": "Try the demo scan",
"Proberen onbevoegde toegang te krijgen tot enig onderdeel van de Dienst of gegevens van andere gebruikers": "Attempting to gain unauthorized access to any part of the Service or other users' data",
"Rechtsgrond: gerechtvaardigd belang (art. 6 lid 1 sub f AVG) · Bewaartermijn: 90 dagen": "Legal basis: legitimate interest (art. 6(1)(f) GDPR) · Retention: 90 days",
"Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: 90 dagen in maillogboeken": "Legal basis: contract (art. 6(1)(b) GDPR) · Retention: 90 days in mail logs",
"Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: tot accountverwijdering + 30 dagen": "Legal basis: contract (art. 6(1)(b) GDPR) · Retention: until account deletion + 30 days",
"Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: tot verwijdering door jou, of tot 30 dagen na accountopzegging": "Legal basis: contract (art. 6(1)(b) GDPR) · Retention: until deleted by you, or up to 30 days after account cancellation",
"Rechtsgrond: toestemming (art. 6 lid 1 sub a AVG) · Bewaartermijn: niet permanent opgeslagen bij de provider": "Legal basis: consent (art. 6(1)(a) GDPR) · Retention: not permanently stored by the provider",
"Rechtsgrond: wettelijke verplichting (art. 6 lid 1 sub c AVG) · Bewaartermijn: 7 jaar (Nederlandse belastingwet)": "Legal basis: legal obligation (art. 6(1)(c) GDPR) · Retention: 7 years (Dutch tax law)",
"Regel het vandaag nog. Niet pas als het te laat is.": "Sort it out today. Not once it's too late.",
"Regelmatige geautomatiseerde back-ups": "Regular automated backups",
"Reminder": "Reminder",
"Reminders": "Reminders",
"Reverse-engineeren, decompileren of afgeleide werken maken van de Dienst": "Reverse-engineering, decompiling, or creating derivative works of the Service",
"Rustig de tijd om een herhalingscursus in te plannen.": "Plenty of time to schedule a refresher course.",
"Scan opnieuw": "Scan again",
"Schakel je automatische tekstherkenning in, dan wordt het document dat je op dat moment uploadt gestuurd naar de Europese provider die jij zelf kiest: Klippa (Nederland) of Mistral AI (Frankrijk). Zonder deze instelling gebeurt dit niet.": "If you enable automatic text recognition, the document you upload at that moment is sent to the European provider you choose: Klippa (Netherlands) or Mistral AI (France). Without this setting, this does not happen.",
"Security": "Security",
"Security & privacy": "Security & privacy",
"Security & privacy als fundament,": "Security & privacy as a foundation,",
"Servicecommunicatie": "Service communication",
"Sleep je certificaat in Certified en de OCR-engine herkent automatisch de titel, uitgever en vervaldatum. Geen overtypwerk, geen tikfouten in vervaldatums — en jij kiest zelf welke Europese OCR-provider je document verwerkt.": "Drag your certificate into Certified and the OCR engine automatically recognizes the title, issuer, and expiry date. No retyping, no typos in expiry dates — and you choose which European OCR provider processes your document.",
"Slimme e-mailreminders": "Smart email reminders",
"Slimme reminders": "Smart reminders",
"Soorten certificaten": "Types of certificates",
"Staat je vraag er niet tussen? Mail ons via support@certified.eu.": "Don't see your question? Email us at support@certified.eu.",
"Standaard ontvang je twee maanden van tevoren een e-mail, en daarna opnieuw zolang je het certificaat niet hebt verlengd. Verloopt een certificaat vandaag, dan krijg je direct een urgente melding. Het aantal maanden stel je zelf in bij je instellingen.": "By default you receive an email two months in advance, and again as long as you haven't renewed the certificate. If a certificate expires today, you get an urgent notification immediately. You set the number of months yourself in your settings.",
"Start gratis": "Start for free",
"Start gratis en groei mee wanneer je meer certificaten beheert. Elk pakket bevat OCR, reminders en veilig delen.": "Start for free and grow along as you manage more certificates. Every plan includes OCR, reminders, and secure sharing.",
"Start met Onbeperkt": "Start with Unlimited",
"Start met Starter": "Start with Starter",
"Status": "Status",
"Stop met certificaten mailen als losse bijlage": "Stop emailing certificates as loose attachments",
"Strikte scheiding tussen accounts": "Strict separation between accounts",
"Sub-verwerker": "Sub-processor",
"Sub-verwerkers": "Sub-processors",
"TLS voor al het verkeer, versleutelde opslag van API-sleutels": "TLS for all traffic, encrypted storage of API keys",
"Tijdelijke ondertekende URL die na 48 uur automatisch verloopt": "Temporary signed URL that automatically expires after 48 hours",
"Titel": "Title",
"Titel, uitgever en vervaldatum automatisch herkend.": "Title, issuer, and expiry date recognized automatically.",
"Titel, uitgever, certificaatnummer, uitgifte- en vervaldatum, notities en het geüploade document zelf — soms bijzondere persoonsgegevens, zoals gegevens over het strafrechtelijk verleden bij een VOG.": "Title, issuer, certificate number, issue and expiry date, notes, and the uploaded document itself — sometimes special category personal data, such as data on criminal history in a Certificate of Conduct.",
"Toegang & isolatie": "Access & isolation",
"Toegangsscheiding per account, afgedwongen op databaseniveau": "Access separation per account, enforced at the database level",
"Uitgever": "Issuer",
"Uitsluitend in Europese datacenters, onder Europees recht. We gebruiken geen Amerikaanse cloudproviders, waardoor je data niet onder de Amerikaanse CLOUD Act valt. Zo voldoe je aantoonbaar aan de AVG.": "Exclusively in European data centers, under European law. We do not use American cloud providers, so your data does not fall under the American CLOUD Act. This way you can demonstrably comply with the GDPR.",
"Upgraden kan op elk moment; je betaalt alleen het verschil voor de rest van het jaar. Downgraden of opzeggen gaat in bij de volgende verlengingsdatum. Na opzegging kun je al je data exporteren; daarna verwijderen we alles definitief binnen 30 dagen.": "You can upgrade at any time; you only pay the difference for the rest of the year. Downgrading or cancelling takes effect on the next renewal date. After cancelling you can export all your data; after that we permanently delete everything within 30 days.",
"Upgraden kan op elk moment; je betaalt dan alleen het verschil voor de resterende looptijd.": "You can upgrade at any time; you then only pay the difference for the remaining term.",
"Upload een foto of PDF, wij vullen de rest in": "Upload a photo or PDF, we fill in the rest",
"Urgent": "Urgent",
"VCA & veiligheidscertificaten": "Safety & workplace certificates",
"VOG": "Certificate of Conduct",
"Vakdiploma's (Code 95, ADR, heftruck)": "Professional certificates (driver training, hazmat, forklift)",
"Valt mijn data onder de Amerikaanse CLOUD Act?": "Does my data fall under the American CLOUD Act?",
"Van scan tot reminder tot veilig delen": "From scan to reminder to secure sharing",
"Vandaag verlopen": "Expired today",
"Veelgestelde vragen": "Frequently asked questions",
"Veilig delen": "Secure sharing",
"Veilig delen via signed URL": "Secure sharing via signed URL",
"Velden invullen…": "Filling in fields…",
"Verenigde Staten": "United States",
"Verify a certificate": "Verify a certificate",
"Verloopt": "Expires",
"Verloopt bijna": "Expiring soon",
"Verloopt over 48 uur": "Expires in 48 hours",
"Verlopen": "Expired",
"Verordening (EU) 2016/679, zoals geïmplementeerd in Nederlands recht via de Uitvoeringswet AVG.": "Regulation (EU) 2016/679, as implemented in Dutch law via the GDPR Implementation Act.",
"Versie 1.0 · augustus 2026": "Version 1.0 · August 2026",
"Versie 1.0 · augustus 2026 · AVG/GDPR": "Version 1.0 · August 2026 · GDPR",
"Versie 1.0 · augustus 2026 · AVG/GDPR art. 28": "Version 1.0 · August 2026 · GDPR art. 28",
"Versleuteld in rust, onderweg en bij het delen": "Encrypted at rest, in transit, and when sharing",
"Versleutelde back-ups binnen de EU": "Encrypted backups within the EU",
"Versleuteling": "Encryption",
"Versleuteling van persoonsgegevens tijdens verzending (TLS) en in rust (AES-256)": "Encryption of personal data in transit (TLS) and at rest (AES-256)",
"Vertrouwelijkheid": "Confidentiality",
"Vervaldatum": "Expiry date",
"Verwerker": "Processor",
"Verwerkersovereenkomst": "Data Processing Agreement",
"Verwerkersovereenkomst op aanvraag": "Data Processing Agreement on request",
"Verwerkingsverantwoordelijke": "Controller",
"Voor Resend (doorgifte naar de Verenigde Staten) steunen we op Standaardcontractuele Clausules op grond van Hoofdstuk V AVG. De verwerker legt elke sub-verwerker gelijkwaardige gegevensbeschermingsverplichtingen op als in deze overeenkomst.": "For Resend (transfer to the United States) we rely on Standard Contractual Clauses under Chapter V GDPR. The processor imposes data protection obligations on every sub-processor equivalent to those in this agreement.",
"Voor altijd gratis, geen creditcard nodig": "Free forever, no credit card required",
"Voor de automatische tekstherkenning kies je zelf een verwerker: Klippa (Nederland) of Mistral AI (Frankrijk) — beide Europese bedrijven onder Europese jurisdictie. Wil je helemaal geen externe verwerking? Zet OCR dan simpelweg uit en vul velden handmatig in. Jouw data, jouw keuze.": "For automatic text recognition, you choose the processor yourself: Klippa (Netherlands) or Mistral AI (France) — both European companies under European jurisdiction. Don't want any external processing at all? Simply turn off OCR and fill in fields manually. Your data, your choice.",
"Voor de doorgifte aan Resend (Verenigde Staten) steunen we op Standaardcontractuele Clausules op grond van Hoofdstuk V AVG. Klippa, Mistral AI en Moneybird zijn allen binnen de EU gevestigd.": "For the transfer to Resend (United States) we rely on Standard Contractual Clauses under Chapter V GDPR. Klippa, Mistral AI, and Moneybird are all located within the EU.",
"Voor elk certificaat dat je zelf bijhoudt": "For every certificate you keep track of yourself",
"Voor iedereen die zijn eigen certificaten, diploma's en VOG's wil bijhouden": "For anyone who wants to keep track of their own certificates, diplomas, and Certificates of Conduct",
"Voor wie Certified wil proberen of maar een handvol certificaten beheert.": "For anyone who wants to try Certified or only manages a handful of certificates.",
"Voor wie al zijn diploma's, VOG's en certificaten voorgoed op één plek wil bewaken.": "For anyone who wants to keep all their diplomas, Certificates of Conduct, and certificates safe in one place for good.",
"Voor wie meer dan een handvol eigen certificaten en diploma's bijhoudt.": "For anyone who keeps track of more than a handful of their own certificates and diplomas.",
"Voor zover wettelijk toegestaan zijn wij niet aansprakelijk voor indirecte schade of gevolgschade. Onze totale aansprakelijkheid is beperkt tot het bedrag dat je in de twaalf maanden voorafgaand aan de schadeveroorzakende gebeurtenis aan ons hebt betaald.": "To the extent permitted by law, we are not liable for indirect or consequential damages. Our total liability is limited to the amount you paid us in the twelve months preceding the event that caused the damage.",
"Voorbeeld van de reminder-tijdlijn": "Example of the reminder timeline",
"Vraag een getekend exemplaar aan": "Request a signed copy",
"Vragen over deze verklaring of je gegevens? Mail ons via": "Questions about this policy or your data? Email us at",
"Vragen over deze voorwaarden?": "Questions about these terms?",
"Vragen over prijzen": "Questions about pricing",
"Vragen over security": "Questions about security",
"Waar je gegevens worden opgeslagen": "Where your data is stored",
"Waar wordt mijn data opgeslagen?": "Where is my data stored?",
"Wachtwoorden opgeslagen als hash, nooit in leesbare tekst": "Passwords stored as a hash, never in readable text",
"Wanneer krijg ik een reminder als een certificaat bijna verloopt?": "When do I get a reminder when a certificate is about to expire?",
"Wat gebeurt er als ik over mijn certificatenlimiet ga?": "What happens if I go over my certificate limit?",
"Wat gebeurt er met een document tijdens de OCR-scan?": "What happens to a document during the OCR scan?",
"We behouden ons het recht voor om toegang op te schorten zonder voorafgaande kennisgeving wanneer we een schending van dit artikel constateren of vermoeden.": "We reserve the right to suspend access without prior notice when we detect or suspect a violation of this article.",
"We kunnen deze verklaring van tijd tot tijd bijwerken. Bij wezenlijke wijzigingen informeren we je per e-mail, ten minste 14 dagen voordat de wijziging ingaat.": "We may update this policy from time to time. For material changes, we will notify you by email at least 14 days before the change takes effect.",
"We kunnen deze voorwaarden wijzigen door een bijgewerkte versie op deze pagina te plaatsen. Bij ingrijpende wijzigingen informeren we je per e-mail, ten minste 30 dagen voordat de wijziging ingaat. Voortgezet gebruik na die datum geldt als acceptatie.": "We may change these terms by posting an updated version on this page. For significant changes, we will notify you by email at least 30 days before the change takes effect. Continued use after that date constitutes acceptance.",
"We kunnen onze prijzen aanpassen met een schriftelijke kennisgeving van ten minste 30 dagen vooraf. Ga je niet akkoord met de nieuwe prijs, dan kun je opzeggen voordat de wijziging ingaat.": "We may adjust our prices with at least 30 days' written notice in advance. If you do not agree with the new price, you can cancel before the change takes effect.",
"We mogen geanonimiseerde, geaggregeerde gegevens die niet tot jou herleidbaar zijn gebruiken om de Dienst te verbeteren.": "We may use anonymized, aggregated data that cannot be traced back to you to improve the Service.",
"We streven naar een betrouwbaar beschikbare Dienst, maar garanderen geen ononderbroken of foutloze toegang. We kunnen de Dienst van tijd tot tijd aanpassen; ingrijpende wijzigingen kondigen we vooraf aan.": "We strive to keep the Service reliably available, but do not guarantee uninterrupted or error-free access. We may adjust the Service from time to time; we announce significant changes in advance.",
"We verkopen, verhuren of verhandelen je persoonsgegevens nooit aan derden. We delen gegevens uitsluitend met de volgende partijen, voor zover van toepassing op jouw gebruik:": "We never sell, rent, or trade your personal data to third parties. We only share data with the following parties, to the extent applicable to your use:",
"We versleutelen documenten in rust en tijdens verzending (TLS), scheiden accounts op databaseniveau en ondersteunen phishing-bestendige aanmelding via passkeys. Gedeelde documenten zijn alleen bereikbaar via cryptografisch ondertekende links die automatisch verlopen. Meer details vind je op onze security-pagina. Mochten we een datalek ontdekken met waarschijnlijk hoog risico voor jouw rechten, dan informeren we je zonder onredelijke vertraging conform artikel 34 AVG.": "We encrypt documents at rest and in transit (TLS), separate accounts at the database level, and support phishing-resistant sign-in via passkeys. Shared documents are only reachable via cryptographically signed links that expire automatically. More details can be found on our security page. Should we discover a data breach likely to pose a high risk to your rights, we will inform you without undue delay in accordance with article 34 GDPR.",
"We versturen reminders en accountmeldingen via Resend (Verenigde Staten). Open- en kliktracking staat uit; Resend verwerkt alleen het ontvangeradres en de berichtinhoud.": "We send reminders and account notifications via Resend (United States). Open and click tracking is disabled; Resend only processes the recipient address and message content.",
"Welke gegevens we verzamelen en waarom": "What data we collect and why",
"Werk je als zzp'er of eenmanszaak en beheer je via Certified ook certificaten van anderen (bijvoorbeeld van jezelf als ondernemer richting een opdrachtgever), dan kun je de onderstaande standaardovereenkomst gebruiken — zonder extra kosten.": "If you work as a freelancer or sole proprietor and also manage certificates of others through Certified (for example your own, as a business owner, toward a client), you can use the standard agreement below — at no extra cost.",
"Werkt met PDF, JPG en PNG — ook met scans en foto's": "Works with PDF, JPG, and PNG — including scans and photos",
"Wie kan er bij mijn certificaten?": "Who can access my certificates?",
"Wie wij zijn": "Who we are",
"Wij behouden alle intellectuele eigendomsrechten op de Dienst zelf, waaronder de broncode, het ontwerp en onze merken.": "We retain all intellectual property rights to the Service itself, including the source code, design, and our trademarks.",
"Wijzigingen": "Changes",
"Wissel tussen licht en donker thema": "Switch between light and dark theme",
"Zeker. Het Gratis-pakket is voor altijd gratis en laat je tot 5 certificaten beheren — zonder creditcard. Groei je eruit, dan upgrade je eenvoudig naar Starter (20 certificaten) of Onbeperkt.": "Sure. The Free plan is free forever and lets you manage up to 5 certificates — no credit card. If you outgrow it, you can easily upgrade to Starter (20 certificates) or Unlimited.",
"Zijn er kosten per OCR-scan?": "Are there costs per OCR scan?",
"Zo mis je nooit de deadline om het op tijd te verlengen.": "That way you never miss the deadline to renew it in time.",
"Zolang je deze Overeenkomst naleeft, verleent de Aanbieder je een beperkt, niet-overdraagbaar recht om de Dienst te gebruiken.": "As long as you comply with this Agreement, the Provider grants you a limited, non-transferable right to use the Service.",
"alle informatie als gedefinieerd in artikel 4 lid 1 AVG.": "all information as defined in article 4(1) GDPR.",
"de klant, die de doeleinden en middelen van de verwerking bepaalt.": "the customer, who determines the purposes and means of the processing.",
"de verwerker helpt bij verzoeken van betrokkenen en bij naleving van de artikelen 3236 AVG.": "the processor assists with data subject requests and with compliance with articles 3236 GDPR.",
"de verwerker schakelt geen nieuwe sub-verwerkers in zonder de verwerkingsverantwoordelijke hierover ten minste 14 dagen vooraf te informeren.": "the processor does not engage new sub-processors without informing the controller at least 14 days in advance.",
"een beveiligingsincident zoals gedefinieerd in artikel 4 lid 12 AVG.": "a security incident as defined in article 4(12) GDPR.",
"een derde partij die door de verwerker wordt ingeschakeld om specifieke verwerkingsactiviteiten uit te voeren.": "a third party engaged by the processor to carry out specific processing activities.",
"jaar": "year",
"niet als bijzaak": "not an afterthought",
"oude manier": "old way",
"passende technische en organisatorische maatregelen, zoals beschreven in artikel 8.": "appropriate technical and organizational measures, as described in article 8.",
"personen die persoonsgegevens verwerken zijn gebonden aan geheimhouding.": "individuals who process personal data are bound by confidentiality."
}

View file

@ -31,6 +31,7 @@
"Authenticating...": "Bezig met authenticeren...", "Authenticating...": "Bezig met authenticeren...",
"Authentication code": "Authenticatiecode", "Authentication code": "Authenticatiecode",
"Back": "Terug", "Back": "Terug",
"Back to certificates": "Terug naar certificaten",
"By category": "Per categorie", "By category": "Per categorie",
"Cancel": "Annuleren", "Cancel": "Annuleren",
"Categories": "Categorieën", "Categories": "Categorieën",
@ -38,7 +39,9 @@
"Category created.": "Categorie aangemaakt.", "Category created.": "Categorie aangemaakt.",
"Category deleted. Its certificates are now uncategorized.": "Categorie verwijderd. De certificaten zijn nu ongecategoriseerd.", "Category deleted. Its certificates are now uncategorized.": "Categorie verwijderd. De certificaten zijn nu ongecategoriseerd.",
"Certificate added.": "Certificaat toegevoegd.", "Certificate added.": "Certificaat toegevoegd.",
"Certificate authenticity check powered by :app.": "Certificaatcontrole aangeboden door :app.",
"Certificate deleted.": "Certificaat verwijderd.", "Certificate deleted.": "Certificaat verwijderd.",
"Certificate details": "Certificaatgegevens",
"Certificate expires today: :title": "Certificaat verloopt vandaag: :title", "Certificate expires today: :title": "Certificaat verloopt vandaag: :title",
"Certificate expiring soon: :title": "Certificaat verloopt binnenkort: :title", "Certificate expiring soon: :title": "Certificaat verloopt binnenkort: :title",
"Certificate number": "Certificaatnummer", "Certificate number": "Certificaatnummer",
@ -46,6 +49,7 @@
"Certificate updated.": "Certificaat bijgewerkt.", "Certificate updated.": "Certificaat bijgewerkt.",
"Certificates": "Certificaten", "Certificates": "Certificaten",
"Certificates shared with you": "Certificaten met je gedeeld", "Certificates shared with you": "Certificaten met je gedeeld",
"Check certificate": "Certificaat controleren",
"Choose the language used throughout the application": "Kies de taal die in de hele applicatie wordt gebruikt", "Choose the language used throughout the application": "Kies de taal die in de hele applicatie wordt gebruikt",
"Click here to re-send the verification email.": "Klik hier om de verificatie-e-mail opnieuw te versturen.", "Click here to re-send the verification email.": "Klik hier om de verificatie-e-mail opnieuw te versturen.",
"Close": "Sluiten", "Close": "Sluiten",
@ -94,6 +98,7 @@
"Ensure your account is using a long, random password to stay secure": "Zorg dat je account een lang, willekeurig wachtwoord gebruikt om veilig te blijven", "Ensure your account is using a long, random password to stay secure": "Zorg dat je account een lang, willekeurig wachtwoord gebruikt om veilig te blijven",
"Enter the 6-digit code from your authenticator app.": "Voer de 6-cijferige code uit je authenticator-app in.", "Enter the 6-digit code from your authenticator app.": "Voer de 6-cijferige code uit je authenticator-app in.",
"Enter the authentication code provided by your authenticator application.": "Voer de authenticatiecode in die door je authenticator-app wordt gegeven.", "Enter the authentication code provided by your authenticator application.": "Voer de authenticatiecode in die door je authenticator-app wordt gegeven.",
"Enter the certificate number and the last name of the certificate holder to check whether it is genuine and currently valid.": "Voer het certificaatnummer en de achternaam van de certificaathouder in om te controleren of het certificaat echt en nog geldig is.",
"Enter the password you received from the sender to view the shared certificates.": "Voer het wachtwoord in dat je van de afzender hebt ontvangen om de gedeelde certificaten te bekijken.", "Enter the password you received from the sender to view the shared certificates.": "Voer het wachtwoord in dat je van de afzender hebt ontvangen om de gedeelde certificaten te bekijken.",
"Enter your API key": "Voer je API-sleutel in", "Enter your API key": "Voer je API-sleutel in",
"Enter your details below to create your account": "Vul hieronder je gegevens in om je account aan te maken", "Enter your details below to create your account": "Vul hieronder je gegevens in om je account aan te maken",
@ -111,7 +116,9 @@
"Hello :name,": "Hallo :name,", "Hello :name,": "Hallo :name,",
"Here is the status of your certificates.": "Dit is de status van je certificaten.", "Here is the status of your certificates.": "Dit is de status van je certificaten.",
"Hide recovery codes": "Herstelcodes verbergen", "Hide recovery codes": "Herstelcodes verbergen",
"Holder last name": "Achternaam certificaathouder",
"If you did not expect this email, you can safely ignore it.": "Als je deze e-mail niet verwachtte, kun je hem veilig negeren.", "If you did not expect this email, you can safely ignore it.": "Als je deze e-mail niet verwachtte, kun je hem veilig negeren.",
"Invalid": "Ongeldig",
"Issue date": "Uitgiftedatum", "Issue date": "Uitgiftedatum",
"Issuing authority": "Uitgevende instantie", "Issuing authority": "Uitgevende instantie",
"Language": "Taal", "Language": "Taal",
@ -141,6 +148,8 @@
"No certificates found": "Geen certificaten gevonden", "No certificates found": "Geen certificaten gevonden",
"No certificates in this category. Try another filter.": "Geen certificaten in deze categorie. Probeer een ander filter.", "No certificates in this category. Try another filter.": "Geen certificaten in deze categorie. Probeer een ander filter.",
"No certificates yet": "Nog geen certificaten", "No certificates yet": "Nog geen certificaten",
"No currently valid certificate matches that certificate number and last name.": "Er is geen geldig certificaat dat overeenkomt met dit certificaatnummer en deze achternaam.",
"No document was uploaded for this certificate.": "Er is geen document geüpload voor dit certificaat.",
"No passkeys yet": "Nog geen passkeys", "No passkeys yet": "Nog geen passkeys",
"No shares yet": "Nog niets gedeeld", "No shares yet": "Nog niets gedeeld",
"None (manual entry)": "Geen (handmatig invoeren)", "None (manual entry)": "Geen (handmatig invoeren)",
@ -163,6 +172,7 @@
"Password": "Wachtwoord", "Password": "Wachtwoord",
"Password updated.": "Wachtwoord bijgewerkt.", "Password updated.": "Wachtwoord bijgewerkt.",
"PDF or image, up to 10 MB.": "PDF of afbeelding, tot 10 MB.", "PDF or image, up to 10 MB.": "PDF of afbeelding, tot 10 MB.",
"Please complete the verification challenge.": "Voltooi de verificatiecontrole.",
"Please confirm access to your account by entering one of your emergency recovery codes.": "Bevestig toegang tot je account door een van je noodherstelcodes in te voeren.", "Please confirm access to your account by entering one of your emergency recovery codes.": "Bevestig toegang tot je account door een van je noodherstelcodes in te voeren.",
"Please enter your new password below": "Voer hieronder je nieuwe wachtwoord in", "Please enter your new password below": "Voer hieronder je nieuwe wachtwoord in",
"Please renew it in good time to stay compliant.": "Verleng het op tijd om compliant te blijven.", "Please renew it in good time to stay compliant.": "Verleng het op tijd om compliant te blijven.",
@ -225,7 +235,9 @@
"The recipient can view and download the certificates on this page:": "De ontvanger kan de certificaten bekijken en downloaden op deze pagina:", "The recipient can view and download the certificates on this page:": "De ontvanger kan de certificaten bekijken en downloaden op deze pagina:",
"The selected certificates have no files to share.": "De geselecteerde certificaten hebben geen bestanden om te delen.", "The selected certificates have no files to share.": "De geselecteerde certificaten hebben geen bestanden om te delen.",
"The ZIP archive is encrypted with the same password you used to open this page.": "Het ZIP-archief is versleuteld met hetzelfde wachtwoord waarmee je deze pagina hebt geopend.", "The ZIP archive is encrypted with the same password you used to open this page.": "Het ZIP-archief is versleuteld met hetzelfde wachtwoord waarmee je deze pagina hebt geopend.",
"The verification challenge failed. Please try again.": "De verificatiecontrole is mislukt. Probeer het opnieuw.",
"There are no files available in this share anymore.": "Er zijn geen bestanden meer beschikbaar op deze pagina.", "There are no files available in this share anymore.": "Er zijn geen bestanden meer beschikbaar op deze pagina.",
"This certificate number and last name match a currently valid certificate.": "Dit certificaatnummer en deze achternaam komen overeen met een geldig certificaat.",
"This is a secure area of the application. Please confirm your password before continuing.": "Dit is een beveiligd gedeelte van de applicatie. Bevestig je wachtwoord voordat je verdergaat.", "This is a secure area of the application. Please confirm your password before continuing.": "Dit is een beveiligd gedeelte van de applicatie. Bevestig je wachtwoord voordat je verdergaat.",
"This page is available until :date, after which it will stop working.": "Deze pagina is beschikbaar tot :date, daarna werkt deze niet meer.", "This page is available until :date, after which it will stop working.": "Deze pagina is beschikbaar tot :date, daarna werkt deze niet meer.",
"This share is no longer available": "Deze gedeelde pagina is niet meer beschikbaar", "This share is no longer available": "Deze gedeelde pagina is niet meer beschikbaar",
@ -247,7 +259,9 @@
"Upload a document to preview it, then confirm the details — pre-filled automatically if scanning is enabled.": "Upload een document om het te bekijken en bevestig daarna de gegevens — automatisch ingevuld als scannen is ingeschakeld.", "Upload a document to preview it, then confirm the details — pre-filled automatically if scanning is enabled.": "Upload een document om het te bekijken en bevestig daarna de gegevens — automatisch ingevuld als scannen is ingeschakeld.",
"Upload your first certificate to start tracking issue dates, expiry, and reminders.": "Upload je eerste certificaat om uitgiftedatums, vervaldatums en herinneringen bij te houden.", "Upload your first certificate to start tracking issue dates, expiry, and reminders.": "Upload je eerste certificaat om uitgiftedatums, vervaldatums en herinneringen bij te houden.",
"Uploading…": "Uploaden…", "Uploading…": "Uploaden…",
"Used by the public verification page.": "Wordt gebruikt op de openbare verificatiepagina.",
"Valid": "Geldig", "Valid": "Geldig",
"Verify a certificate": "Certificaat verifiëren",
"Verify authentication code": "Authenticatiecode verifiëren", "Verify authentication code": "Authenticatiecode verifiëren",
"View all": "Alles bekijken", "View all": "Alles bekijken",
"View recovery codes": "Herstelcodes bekijken", "View recovery codes": "Herstelcodes bekijken",

View file

@ -19,6 +19,7 @@
</source> </source>
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="APP_LOCALE" value="en"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/> <env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/> <env name="BROADCAST_CONNECTION" value="null"/>

View file

@ -0,0 +1,212 @@
@extends('marketing.layout')
@section('title', __('Verwerkersovereenkomst').' — Certified')
@section('description', __('De standaard verwerkersovereenkomst (DPA) van Certified, beschikbaar voor zzp\'ers en eenmanszaken die er één nodig hebben.'))
@section('content')
@include('marketing.partials.legal-header', ['title' => __('Verwerkersovereenkomst'), 'updated' => __('Versie 1.0 · augustus 2026 · AVG/GDPR art. 28')])
<section class="mx-auto max-w-3xl px-4 pb-8 sm:px-6">
<div class="rounded-2xl border border-emerald-200/70 bg-emerald-50/60 p-6 sm:p-8 dark:border-emerald-500/25 dark:bg-emerald-500/5">
<h2 class="text-lg font-bold text-slate-900 dark:text-white">{{ __('Heb je deze wel nodig?') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __("Certified is gebouwd voor persoonlijk gebruik: jij beheert je eigen VOG, diploma's en certificaten. In dat geval ben je zelf geen verwerkingsverantwoordelijke in de zin van de AVG en heb je deze overeenkomst niet nodig.") }}
</p>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Werk je als zzp\'er of eenmanszaak en beheer je via Certified ook certificaten van anderen (bijvoorbeeld van jezelf als ondernemer richting een opdrachtgever), dan kun je de onderstaande standaardovereenkomst gebruiken — zonder extra kosten.') }}
</p>
<a
href="mailto:privacy@bckstage.cloud?subject=Verwerkersovereenkomst%20Certified"
class="mt-5 inline-flex items-center gap-2 rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white shadow-md shadow-emerald-600/25 transition-all duration-200 hover:-translate-y-px hover:bg-emerald-500"
>
{{ __('Vraag een getekend exemplaar aan') }}
</a>
</div>
</section>
<section class="mx-auto max-w-3xl px-4 pb-12 sm:px-6">
<div class="grid gap-4 sm:grid-cols-2">
@include('marketing.partials.legal-party-block', ['label' => __('Verwerker')])
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-5 dark:border-slate-800 dark:bg-slate-900/60">
<p class="text-xs font-bold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">{{ __('Verwerkingsverantwoordelijke') }}</p>
<p class="mt-2 text-base font-semibold text-slate-900 dark:text-white">{{ __('De klant') }}</p>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De zzp\'er of eenmanszaak die Certified gebruikt en zelf certificaatgegevens van anderen verwerkt.') }}
</p>
</div>
</div>
</section>
<section class="mx-auto max-w-3xl px-4 pb-24 sm:px-6">
<div class="space-y-10">
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 1 — Definities') }}</h2>
<ul class="mt-3 space-y-2 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"AVG"/"GDPR"') }}</span> {{ __('Verordening (EU) 2016/679, zoals geïmplementeerd in Nederlands recht via de Uitvoeringswet AVG.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"Persoonsgegevens"') }}</span> {{ __('alle informatie als gedefinieerd in artikel 4 lid 1 AVG.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"Verwerkingsverantwoordelijke"') }}</span> {{ __('de klant, die de doeleinden en middelen van de verwerking bepaalt.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"Verwerker"') }}</span> {{ __('Bckstage, die persoonsgegevens verwerkt namens de verwerkingsverantwoordelijke.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"Sub-verwerker"') }}</span> {{ __('een derde partij die door de verwerker wordt ingeschakeld om specifieke verwerkingsactiviteiten uit te voeren.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('"Inbreuk in verband met persoonsgegevens"') }}</span> {{ __('een beveiligingsincident zoals gedefinieerd in artikel 4 lid 12 AVG.') }}</li>
</ul>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 2 — Onderwerp, aard en doel') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>2.1 {{ __('De verwerker verwerkt persoonsgegevens uitsluitend om Certified te leveren aan de verwerkingsverantwoordelijke: opslag, OCR-herkenning, het bewaken van vervaldatums en het veilig delen van certificaten.') }}</p>
<p>2.2 {{ __('De verwerker verwerkt persoonsgegevens niet voor eigen doeleinden, reclame, profilering of enig ander doel dan het leveren en verbeteren van Certified.') }}</p>
<p>2.3 {{ __('De verwerker handelt uitsluitend op gedocumenteerde instructies van de verwerkingsverantwoordelijke, tenzij Unierecht of nationale wetgeving anders vereist.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 3 — Looptijd') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Deze overeenkomst blijft van kracht gedurende de looptijd van het abonnement en eindigt automatisch bij beëindiging daarvan, onder voorbehoud van artikel 11 (Verwijdering en teruggave).') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 4 — Persoonsgegevens en betrokkenen') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Afhankelijk van hoe de verwerkingsverantwoordelijke Certified gebruikt, kunnen de volgende gegevens worden verwerkt: naam, contactgegevens, certificaatgegevens (titel, uitgever, geldigheidsdata) en het geüploade document zelf.') }}
</p>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('4.1 De verwerker erkent dat Certified mede bedoeld is voor het opslaan van documenten die bijzondere persoonsgegevens kunnen bevatten in de zin van artikel 9 en 10 AVG, zoals gegevens over het strafrechtelijk verleden bij een Verklaring Omtrent het Gedrag (VOG). De verwerkingsverantwoordelijke is zelf verantwoordelijk voor het hebben van een geldige rechtsgrond wanneer hij dergelijke gegevens van anderen dan zichzelf verwerkt.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 5 — Verplichtingen van de verwerker') }}</h2>
<ul class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('Vertrouwelijkheid') }}</span> {{ __('personen die persoonsgegevens verwerken zijn gebonden aan geheimhouding.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('Beveiliging') }}</span> {{ __('passende technische en organisatorische maatregelen, zoals beschreven in artikel 8.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('Sub-verwerkers') }}</span> {{ __('de verwerker schakelt geen nieuwe sub-verwerkers in zonder de verwerkingsverantwoordelijke hierover ten minste 14 dagen vooraf te informeren.') }}</li>
<li><span class="font-medium text-slate-800 dark:text-slate-200">{{ __('Bijstand') }}</span> {{ __('de verwerker helpt bij verzoeken van betrokkenen en bij naleving van de artikelen 3236 AVG.') }}</li>
</ul>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 6 — Sub-verwerkers') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Door deze overeenkomst aan te gaan geeft de verwerkingsverantwoordelijke algemene toestemming aan de verwerker om de volgende sub-verwerkers in te schakelen:') }}
</p>
<div class="mt-4 overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-800">
<table class="w-full text-left text-sm">
<thead class="bg-slate-50 text-xs font-semibold tracking-wide text-slate-500 uppercase dark:bg-slate-900/60 dark:text-slate-400">
<tr>
<th class="px-4 py-3">{{ __('Sub-verwerker') }}</th>
<th class="px-4 py-3">{{ __('Locatie') }}</th>
<th class="px-4 py-3">{{ __('Doel') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-800">
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">{{ __('Eigen infrastructuur') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('EU (Nederland)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Applicatiehosting, bestandsopslag en databases') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Resend</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Verenigde Staten') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Bezorging van reminders en accountmeldingen') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Klippa</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Nederland (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('OCR-herkenning — alleen indien geactiveerd') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Mistral AI</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Frankrijk (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('OCR-herkenning — alleen indien geactiveerd') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Moneybird</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Nederland (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Facturering van betaalde abonnementen') }}</td>
</tr>
</tbody>
</table>
</div>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Voor Resend (doorgifte naar de Verenigde Staten) steunen we op Standaardcontractuele Clausules op grond van Hoofdstuk V AVG. De verwerker legt elke sub-verwerker gelijkwaardige gegevensbeschermingsverplichtingen op als in deze overeenkomst.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 7 — Internationale doorgiften') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De verwerker geeft geen persoonsgegevens door aan derde landen buiten de EER, behalve aan Resend (Verenigde Staten) voor het bezorgen van reminders, op basis van passende waarborgen conform Hoofdstuk V AVG. Alle overige sub-verwerkers zijn gevestigd binnen de EU.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 8 — Beveiligingsmaatregelen') }}</h2>
<ul class="mt-3 space-y-2 text-base leading-relaxed text-slate-600 dark:text-slate-400">
@foreach ([
__('Versleuteling van persoonsgegevens tijdens verzending (TLS) en in rust (AES-256)'),
__('Wachtwoorden opgeslagen als hash, nooit in leesbare tekst'),
__('Toegangsscheiding per account, afgedwongen op databaseniveau'),
__('Ondersteuning voor phishing-bestendige authenticatie via passkeys'),
__('Gedeelde documenten alleen bereikbaar via cryptografisch ondertekende, automatisch verlopende links'),
__('Regelmatige geautomatiseerde back-ups'),
] as $measure)
<li class="flex items-start gap-3">
<svg class="mt-0.5 size-4.5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>
<span>{{ $measure }}</span>
</li>
@endforeach
</ul>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Een volledig overzicht van onze beveiligingsmaatregelen vind je op onze security-pagina.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 9 — Meldplicht datalekken') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De verwerker stelt de verwerkingsverantwoordelijke zonder onredelijke vertraging, en in elk geval binnen 72 uur nadat hij kennis heeft gekregen van een inbreuk in verband met persoonsgegevens, hiervan op de hoogte — met een beschrijving van de aard, de waarschijnlijke gevolgen en de genomen maatregelen.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 10 — Rechten van betrokkenen') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De verwerker helpt de verwerkingsverantwoordelijke bij het beantwoorden van verzoeken van betrokkenen (inzage, rectificatie, verwijdering, beperking, overdraagbaarheid, bezwaar) en stuurt verzoeken die rechtstreeks bij de verwerker binnenkomen binnen 5 werkdagen door.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 11 — Verwijdering en teruggave') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Bij beëindiging van het abonnement verwijdert de verwerker, naar keuze van de verwerkingsverantwoordelijke, alle persoonsgegevens of geeft deze terug in een machineleesbaar formaat. Zonder tijdige instructie verwijdert de verwerker alle gegevens binnen 30 dagen na beëindiging, inclusief back-ups binnen maximaal 90 dagen vanwege back-uprotatieschema\'s.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 12 — Auditrechten') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De verwerker verstrekt de informatie die redelijkerwijs nodig is om naleving van artikel 28 AVG aan te tonen. Auditverzoeken worden schriftelijk ingediend met ten minste 30 dagen vooraankondiging en mogen de beveiliging van gegevens van andere klanten niet in gevaar brengen.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 13 — Aansprakelijkheid en toepasselijk recht') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Aansprakelijkheid wordt geregeld conform artikel 82 AVG en de aansprakelijkheidsbepaling in de Gebruiksvoorwaarden. Op deze overeenkomst is Nederlands recht van toepassing; geschillen worden voorgelegd aan de bevoegde rechtbank te Arnhem.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Ondertekening') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Deze overeenkomst wordt geacht te zijn aanvaard bij eerste gebruik van Certified in de context van je eigen bedrijfsvoering. Voor een getekende kopie voor je eigen archief mail je ons via') }} <a href="mailto:privacy@bckstage.cloud" class="font-medium text-emerald-700 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300">privacy@bckstage.cloud</a>.
</p>
</article>
</div>
</section>
@endsection

View file

@ -1,6 +1,6 @@
@extends('marketing.layout') @extends('marketing.layout')
@section('title', 'Certified — Nooit meer verlopen certificaten. Volledig AVG-proof.') @section('title', 'Certified — '.__('Al je certificaten op één plek. Nooit meer verlopen.'))
@section('content') @section('content')
@ -17,16 +17,16 @@
<span class="absolute inline-flex size-full animate-ping rounded-full bg-emerald-500 opacity-60"></span> <span class="absolute inline-flex size-full animate-ping rounded-full bg-emerald-500 opacity-60"></span>
<span class="relative inline-flex size-2 rounded-full bg-emerald-500"></span> <span class="relative inline-flex size-2 rounded-full bg-emerald-500"></span>
</span> </span>
Gebouwd voor organisaties die met VOG's en persoonsgegevens werken {{ __("Voor iedereen die zijn eigen certificaten, diploma's en VOG's wil bijhouden") }}
</p> </p>
<h1 class="animate-fade-in-up mt-8 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-6xl dark:text-white"> <h1 class="animate-fade-in-up mt-8 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-6xl dark:text-white">
Nooit meer verlopen certificaten. {{ __('Al je certificaten op één plek.') }}
<span class="bg-gradient-to-r from-emerald-600 to-teal-500 bg-clip-text text-transparent dark:from-emerald-400 dark:to-teal-300">Volledig AVG-proof.</span> <span class="bg-gradient-to-r from-emerald-600 to-teal-500 bg-clip-text text-transparent dark:from-emerald-400 dark:to-teal-300">{{ __('Nooit meer per ongeluk verlopen.') }}</span>
</h1> </h1>
<p class="animate-fade-in-up mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-pretty text-slate-600 [animation-delay:200ms] sm:text-xl dark:text-slate-400"> <p class="animate-fade-in-up mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-pretty text-slate-600 [animation-delay:200ms] sm:text-xl dark:text-slate-400">
Certified scant je certificaten automatisch in, bewaakt elke vervaldatum en deelt documenten alleen versleuteld. Zo blijft je organisatie aantoonbaar compliant zonder Excel-lijstjes en losse e-mailbijlagen. {{ __("Certified scant je VOG, diploma's en certificaten automatisch in, bewaakt elke vervaldatum en deelt ze alleen versleuteld — zonder Excel-lijstjes, losse mapjes of kwetsbare e-mailbijlagen.") }}
</p> </p>
<div class="animate-fade-in-up mt-10 flex flex-col items-center justify-center gap-4 [animation-delay:300ms] sm:flex-row"> <div class="animate-fade-in-up mt-10 flex flex-col items-center justify-center gap-4 [animation-delay:300ms] sm:flex-row">
@ -34,17 +34,17 @@
href="{{ route('register') }}" href="{{ route('register') }}"
class="group w-full rounded-xl bg-emerald-600 px-8 py-3.5 text-base font-semibold text-white shadow-lg shadow-emerald-600/25 transition-all duration-200 hover:-translate-y-0.5 hover:bg-emerald-500 hover:shadow-xl hover:shadow-emerald-500/30 active:translate-y-0 sm:w-auto" class="group w-full rounded-xl bg-emerald-600 px-8 py-3.5 text-base font-semibold text-white shadow-lg shadow-emerald-600/25 transition-all duration-200 hover:-translate-y-0.5 hover:bg-emerald-500 hover:shadow-xl hover:shadow-emerald-500/30 active:translate-y-0 sm:w-auto"
> >
Vraag toegang aan {{ __('Start gratis') }}
<span class="inline-block transition-transform duration-200 group-hover:translate-x-1" aria-hidden="true">&rarr;</span> <span class="inline-block transition-transform duration-200 group-hover:translate-x-1" aria-hidden="true">&rarr;</span>
</a> </a>
<a <a
href="#features" href="#features"
class="w-full rounded-xl border border-slate-300 bg-white/60 px-8 py-3.5 text-base font-semibold text-slate-700 backdrop-blur transition-colors duration-200 hover:border-emerald-500 hover:text-emerald-700 sm:w-auto dark:border-slate-700 dark:bg-slate-900/60 dark:text-slate-200 dark:hover:border-emerald-500 dark:hover:text-emerald-400" class="w-full rounded-xl border border-slate-300 bg-white/60 px-8 py-3.5 text-base font-semibold text-slate-700 backdrop-blur transition-colors duration-200 hover:border-emerald-500 hover:text-emerald-700 sm:w-auto dark:border-slate-700 dark:bg-slate-900/60 dark:text-slate-200 dark:hover:border-emerald-500 dark:hover:text-emerald-400"
>Ontdek hoe het werkt</a> >{{ __('Ontdek hoe het werkt') }}</a>
</div> </div>
<p class="animate-fade-in-up mt-6 text-sm text-slate-500 [animation-delay:400ms] dark:text-slate-400"> <p class="animate-fade-in-up mt-6 text-sm text-slate-500 [animation-delay:400ms] dark:text-slate-400">
Gratis tot 5 certificaten &middot; Geen creditcard nodig &middot; 🇪🇺 Europese hosting {{ __('Gratis tot 5 certificaten') }} &middot; {{ __('Geen creditcard nodig') }} &middot; 🇪🇺 {{ __('Europese hosting') }}
</p> </p>
</div> </div>
@ -73,25 +73,25 @@
</span> </span>
<span class="text-xs font-semibold text-slate-700 dark:text-slate-200">Certified</span> <span class="text-xs font-semibold text-slate-700 dark:text-slate-200">Certified</span>
</div> </div>
<div class="rounded-md bg-emerald-50 px-2.5 py-1.5 text-xs font-medium text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400">Certificaten</div> <div class="rounded-md bg-emerald-50 px-2.5 py-1.5 text-xs font-medium text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400">{{ __('Certificaten') }}</div>
<div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">Categorie&euml;n</div> <div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">{{ __('Categorieën') }}</div>
<div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">Reminders</div> <div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">{{ __('Reminders') }}</div>
<div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">Instellingen</div> <div class="px-2.5 py-1.5 text-xs text-slate-400 dark:text-slate-500">{{ __('Instellingen') }}</div>
</div> </div>
<div class="min-w-0 flex-1 p-4 sm:p-6"> <div class="min-w-0 flex-1 p-4 sm:p-6">
{{-- Stat cards --}} {{-- Stat cards --}}
<div class="grid grid-cols-3 gap-3"> <div class="grid grid-cols-3 gap-3">
<div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800"> <div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800">
<p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">Geldig</p> <p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">{{ __('Geldig') }}</p>
<p class="mt-1 text-lg font-bold text-emerald-600 sm:text-2xl dark:text-emerald-400">42</p> <p class="mt-1 text-lg font-bold text-emerald-600 sm:text-2xl dark:text-emerald-400">12</p>
</div> </div>
<div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800"> <div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800">
<p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">Verloopt&nbsp;bijna</p> <p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">{{ __('Verloopt bijna') }}</p>
<p class="mt-1 text-lg font-bold text-amber-500 sm:text-2xl">3</p> <p class="mt-1 text-lg font-bold text-amber-500 sm:text-2xl">1</p>
</div> </div>
<div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800"> <div class="rounded-xl border border-slate-200 p-3 dark:border-slate-800">
<p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">Verlopen</p> <p class="text-[10px] font-medium tracking-wide text-slate-400 uppercase sm:text-xs">{{ __('Verlopen') }}</p>
<p class="mt-1 text-lg font-bold text-red-500 sm:text-2xl">1</p> <p class="mt-1 text-lg font-bold text-red-500 sm:text-2xl">1</p>
</div> </div>
</div> </div>
@ -112,13 +112,13 @@
{{-- Certificate table skeleton --}} {{-- Certificate table skeleton --}}
<div class="mt-4 overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800"> <div class="mt-4 overflow-hidden rounded-xl border border-slate-200 dark:border-slate-800">
<div class="grid grid-cols-[1fr_auto_auto] items-center gap-3 border-b border-slate-200 bg-slate-50 px-4 py-2 text-[10px] font-semibold tracking-wide text-slate-400 uppercase sm:text-xs dark:border-slate-800 dark:bg-slate-950/50"> <div class="grid grid-cols-[1fr_auto_auto] items-center gap-3 border-b border-slate-200 bg-slate-50 px-4 py-2 text-[10px] font-semibold tracking-wide text-slate-400 uppercase sm:text-xs dark:border-slate-800 dark:bg-slate-950/50">
<span>Certificaat</span><span class="hidden sm:block">Verloopt</span><span>Status</span> <span>{{ __('Certificaat') }}</span><span class="hidden sm:block">{{ __('Verloopt') }}</span><span>{{ __('Status') }}</span>
</div> </div>
@foreach ([ @foreach ([
['VOG — J. de Vries', '14-09-2027', 'Geldig', 'emerald'], ['VCA Basis', '14-09-2027', __('Geldig'), 'emerald'],
['BHV Diploma — A. Bakker', '02-08-2026', 'Verloopt bijna', 'amber'], ['EHBO Diploma', '02-08-2026', __('Verloopt bijna'), 'amber'],
['VCA Basis — M. Visser', '28-11-2028', 'Geldig', 'emerald'], ['VOG (Verklaring Omtrent Gedrag)', '28-11-2028', __('Geldig'), 'emerald'],
['Code 95 — P. Jansen', '01-07-2026', 'Verlopen', 'red'], ['Code 95 nascholing', '01-07-2026', __('Verlopen'), 'red'],
] as [$name, $date, $status, $color]) ] as [$name, $date, $status, $color])
<div class="grid grid-cols-[1fr_auto_auto] items-center gap-3 border-b border-slate-100 px-4 py-2.5 text-xs last:border-0 dark:border-slate-800/60"> <div class="grid grid-cols-[1fr_auto_auto] items-center gap-3 border-b border-slate-100 px-4 py-2.5 text-xs last:border-0 dark:border-slate-800/60">
<span class="truncate font-medium text-slate-700 dark:text-slate-300">{{ $name }}</span> <span class="truncate font-medium text-slate-700 dark:text-slate-300">{{ $name }}</span>
@ -140,17 +140,17 @@
</section> </section>
{{-- ============ SOCIAL PROOF STRIP ============ --}} {{-- ============ SOCIAL PROOF STRIP ============ --}}
<section class="border-y border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/40" aria-label="Sectoren"> <section class="border-y border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/40" aria-label="{{ __('Soorten certificaten') }}">
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<p class="text-center text-sm font-medium text-slate-500 dark:text-slate-400"> <p class="text-center text-sm font-medium text-slate-500 dark:text-slate-400">
Ontworpen voor sectoren waar certificaten verplicht zijn {{ __('Voor elk certificaat dat je zelf bijhoudt') }}
</p> </p>
<div class="mt-5 flex flex-wrap items-center justify-center gap-x-10 gap-y-3 text-sm font-semibold tracking-wide text-slate-400 dark:text-slate-500"> <div class="mt-5 flex flex-wrap items-center justify-center gap-x-10 gap-y-3 text-sm font-semibold tracking-wide text-slate-400 dark:text-slate-500">
<span>Kinderopvang</span><span aria-hidden="true">&middot;</span> <span>{{ __('VOG') }}</span><span aria-hidden="true">&middot;</span>
<span>Zorg &amp; welzijn</span><span aria-hidden="true">&middot;</span> <span>{{ __("Diploma's & getuigschriften") }}</span><span aria-hidden="true">&middot;</span>
<span>Bouw &amp; techniek</span><span aria-hidden="true">&middot;</span> <span>{{ __('EHBO & BHV') }}</span><span aria-hidden="true">&middot;</span>
<span>Transport &amp; logistiek</span><span aria-hidden="true">&middot;</span> <span>{{ __('VCA & veiligheidscertificaten') }}</span><span aria-hidden="true">&middot;</span>
<span>Detachering</span> <span>{{ __('Vakdiploma\'s (Code 95, ADR, heftruck)') }}</span>
</div> </div>
</div> </div>
</section> </section>
@ -159,12 +159,12 @@
<section id="features" class="scroll-mt-20 py-20 sm:py-28"> <section id="features" class="scroll-mt-20 py-20 sm:py-28">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl text-center"> <div class="mx-auto max-w-2xl text-center">
<p class="text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">Features</p> <p class="text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">{{ __('Features') }}</p>
<h2 class="mt-3 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white"> <h2 class="mt-3 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Van scan tot reminder tot veilig delen {{ __('Van scan tot reminder tot veilig delen') }}
</h2> </h2>
<p class="mt-4 text-lg leading-relaxed text-slate-500 dark:text-slate-400"> <p class="mt-4 text-lg leading-relaxed text-slate-500 dark:text-slate-400">
Drie pijlers die samen je volledige certificaatbeheer afdekken zonder handwerk. {{ __('Drie pijlers die samen je hele certificatenbeheer afdekken — zonder handwerk.') }}
</p> </p>
</div> </div>
@ -173,19 +173,19 @@
<div> <div>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-bold tracking-wide text-emerald-700 uppercase dark:bg-emerald-500/15 dark:text-emerald-400"> <span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-bold tracking-wide text-emerald-700 uppercase dark:bg-emerald-500/15 dark:text-emerald-400">
<svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 3.75H6A2.25 2.25 0 0 0 3.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0 1 20.25 6v1.5m0 9V18A2.25 2.25 0 0 1 18 20.25h-1.5m-9 0H6A2.25 2.25 0 0 1 3.75 18v-1.5M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg> <svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 3.75H6A2.25 2.25 0 0 0 3.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0 1 20.25 6v1.5m0 9V18A2.25 2.25 0 0 1 18 20.25h-1.5m-9 0H6A2.25 2.25 0 0 1 3.75 18v-1.5M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>
Automatische OCR-scan {{ __('Automatische OCR-scan') }}
</span> </span>
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white"> <h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
Upload een PDF, wij vullen de rest in {{ __('Upload een foto of PDF, wij vullen de rest in') }}
</h3> </h3>
<p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400"> <p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400">
Sleep een certificaat in Certified en de OCR-engine herkent automatisch de titel, uitgever en vervaldatum. Geen overtypwerk, geen tikfouten in vervaldatums en jij kiest zelf welke Europese OCR-provider je data verwerkt. {{ __('Sleep je certificaat in Certified en de OCR-engine herkent automatisch de titel, uitgever en vervaldatum. Geen overtypwerk, geen tikfouten in vervaldatums — en jij kiest zelf welke Europese OCR-provider je document verwerkt.') }}
</p> </p>
<ul class="mt-6 space-y-3 text-base text-slate-600 dark:text-slate-300"> <ul class="mt-6 space-y-3 text-base text-slate-600 dark:text-slate-300">
@foreach ([ @foreach ([
'Herkent titel, uitgever en vervaldatum in seconden', __('Herkent titel, uitgever en vervaldatum in seconden'),
'Keuze uit Europese providers: Klippa of Mistral AI', __('Keuze uit Europese providers: Klippa of Mistral AI'),
'Werkt met PDF, JPG en PNG — ook met scans en foto\'s', __("Werkt met PDF, JPG en PNG — ook met scans en foto's"),
] as $point) ] as $point)
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<svg class="mt-1 size-4.5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg> <svg class="mt-1 size-4.5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>
@ -205,7 +205,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="flex size-8 items-center justify-center rounded-lg bg-red-100 text-[10px] font-bold text-red-600 dark:bg-red-500/15 dark:text-red-400">PDF</span> <span class="flex size-8 items-center justify-center rounded-lg bg-red-100 text-[10px] font-bold text-red-600 dark:bg-red-500/15 dark:text-red-400">PDF</span>
<div> <div>
<p class="text-xs font-semibold text-slate-700 dark:text-slate-200">vca-certificaat.pdf</p> <p class="text-xs font-semibold text-slate-700 dark:text-slate-200">ehbo-diploma.pdf</p>
<p class="text-[10px] text-slate-400">248 kB</p> <p class="text-[10px] text-slate-400">248 kB</p>
</div> </div>
</div> </div>
@ -225,7 +225,7 @@
{{-- Extracted fields --}} {{-- Extracted fields --}}
<div class="flex flex-col justify-between"> <div class="flex flex-col justify-between">
<div class="space-y-3"> <div class="space-y-3">
@foreach ([['title', 'Titel'], ['issuer', 'Uitgever'], ['expires', 'Vervaldatum']] as [$field, $label]) @foreach ([['title', __('Titel')], ['issuer', __('Uitgever')], ['expires', __('Vervaldatum')]] as [$field, $label])
<div> <div>
<p class="text-[10px] font-semibold tracking-wide text-slate-400 uppercase">{{ $label }}</p> <p class="text-[10px] font-semibold tracking-wide text-slate-400 uppercase">{{ $label }}</p>
<div class="mt-1 flex h-8 items-center rounded-lg border border-slate-200 bg-white px-2.5 text-xs font-medium text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100"> <div class="mt-1 flex h-8 items-center rounded-lg border border-slate-200 bg-white px-2.5 text-xs font-medium text-slate-800 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100">
@ -242,7 +242,7 @@
<div class="mt-6"> <div class="mt-6">
<div x-show="state === 'scanning' || state === 'typing'" x-cloak> <div x-show="state === 'scanning' || state === 'typing'" x-cloak>
<div class="flex items-center justify-between text-xs font-medium"> <div class="flex items-center justify-between text-xs font-medium">
<span class="text-slate-500 dark:text-slate-400" x-text="state === 'scanning' ? 'Document scannen…' : 'Velden invullen…'"></span> <span class="text-slate-500 dark:text-slate-400" x-text="state === 'scanning' ? '{{ __('Document scannen…') }}' : '{{ __('Velden invullen…') }}'"></span>
<span class="text-emerald-600 tabular-nums dark:text-emerald-400" x-text="Math.round(progress) + '%'"></span> <span class="text-emerald-600 tabular-nums dark:text-emerald-400" x-text="Math.round(progress) + '%'"></span>
</div> </div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-200 dark:bg-slate-700"> <div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-200 dark:bg-slate-700">
@ -257,12 +257,12 @@
class="group flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-emerald-400/60 px-4 py-3 text-sm font-semibold text-emerald-700 transition-all duration-200 hover:border-emerald-500 hover:bg-emerald-50 dark:text-emerald-400 dark:hover:bg-emerald-500/10" class="group flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-emerald-400/60 px-4 py-3 text-sm font-semibold text-emerald-700 transition-all duration-200 hover:border-emerald-500 hover:bg-emerald-50 dark:text-emerald-400 dark:hover:bg-emerald-500/10"
> >
<svg class="size-4.5 transition-transform duration-200 group-hover:-translate-y-0.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" /></svg> <svg class="size-4.5 transition-transform duration-200 group-hover:-translate-y-0.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" /></svg>
<span x-text="state === 'done' ? 'Scan opnieuw' : 'Probeer de demo-scan'"></span> <span x-text="state === 'done' ? '{{ __('Scan opnieuw') }}' : '{{ __('Probeer de demo-scan') }}'"></span>
</button> </button>
<p x-show="state === 'done'" x-cloak class="mt-3 flex items-center justify-center gap-1.5 text-xs font-medium text-emerald-600 dark:text-emerald-400"> <p x-show="state === 'done'" x-cloak class="mt-3 flex items-center justify-center gap-1.5 text-xs font-medium text-emerald-600 dark:text-emerald-400">
<svg class="size-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /></svg> <svg class="size-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /></svg>
Certificaat herkend en ingevuld klaar om op te slaan {{ __('Certificaat herkend en ingevuld — klaar om op te slaan') }}
</p> </p>
</div> </div>
</div> </div>
@ -273,21 +273,21 @@
<div class="order-1 lg:order-2"> <div class="order-1 lg:order-2">
<span class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-bold tracking-wide text-amber-700 uppercase dark:bg-amber-500/15 dark:text-amber-400"> <span class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-bold tracking-wide text-amber-700 uppercase dark:bg-amber-500/15 dark:text-amber-400">
<svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" /></svg> <svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" /></svg>
Slimme reminders {{ __('Slimme reminders') }}
</span> </span>
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white"> <h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
Op tijd gewaarschuwd, elke keer weer {{ __('Op tijd gewaarschuwd, elke keer weer') }}
</h3> </h3>
<p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400"> <p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400">
Certified controleert dagelijks alle vervaldatums en stuurt automatisch reminders ruim op tijd om een certificaat te verlengen. Verloopt er toch iets? Dan krijg je direct een urgente melding, zodat het nooit onopgemerkt blijft. {{ __('Certified controleert dagelijks alle vervaldatums en stuurt automatisch reminders — ruim op tijd om een certificaat te verlengen. Verloopt er toch iets? Dan krijg je direct een urgente melding, zodat het nooit onopgemerkt blijft.') }}
</p> </p>
<p class="mt-4 text-base text-slate-500 dark:text-slate-400"> <p class="mt-4 text-base text-slate-500 dark:text-slate-400">
Jij bepaalt zelf hoeveel maanden van tevoren je gewaarschuwd wilt worden. {{ __('Jij bepaalt zelf hoeveel maanden van tevoren je gewaarschuwd wilt worden.') }}
</p> </p>
</div> </div>
{{-- Timeline visual --}} {{-- Timeline visual --}}
<div class="relative order-2 lg:order-1" aria-label="Voorbeeld van de reminder-tijdlijn"> <div class="relative order-2 lg:order-1" aria-label="{{ __('Voorbeeld van de reminder-tijdlijn') }}">
<div class="absolute top-0 bottom-0 left-5 w-px bg-gradient-to-b from-emerald-400 via-amber-400 to-red-400 sm:left-6" aria-hidden="true"></div> <div class="absolute top-0 bottom-0 left-5 w-px bg-gradient-to-b from-emerald-400 via-amber-400 to-red-400 sm:left-6" aria-hidden="true"></div>
<div class="space-y-6"> <div class="space-y-6">
@ -298,11 +298,11 @@
</span> </span>
<div class="flex-1 rounded-2xl border border-emerald-200 bg-emerald-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-500/10 sm:p-5 dark:border-emerald-500/25 dark:bg-emerald-500/5"> <div class="flex-1 rounded-2xl border border-emerald-200 bg-emerald-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-500/10 sm:p-5 dark:border-emerald-500/25 dark:bg-emerald-500/5">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<p class="text-xs font-bold tracking-wide text-emerald-700 uppercase dark:text-emerald-400">2 maanden van tevoren</p> <p class="text-xs font-bold tracking-wide text-emerald-700 uppercase dark:text-emerald-400">{{ __('2 maanden van tevoren') }}</p>
<span class="rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">Reminder</span> <span class="rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">{{ __('Reminder') }}</span>
</div> </div>
<p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">&ldquo;BHV Diploma A. Bakker&rdquo; verloopt over 2 maanden</p> <p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">{{ __('Je BHV-diploma verloopt over 2 maanden') }}</p>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">Rustig de tijd om een herhalingscursus in te plannen.</p> <p class="mt-1 text-sm text-slate-500 dark:text-slate-400">{{ __('Rustig de tijd om een herhalingscursus in te plannen.') }}</p>
</div> </div>
</div> </div>
@ -313,11 +313,11 @@
</span> </span>
<div class="flex-1 rounded-2xl border border-amber-200 bg-amber-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-amber-500/10 sm:p-5 dark:border-amber-500/25 dark:bg-amber-500/5"> <div class="flex-1 rounded-2xl border border-amber-200 bg-amber-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-amber-500/10 sm:p-5 dark:border-amber-500/25 dark:bg-amber-500/5">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<p class="text-xs font-bold tracking-wide text-amber-700 uppercase dark:text-amber-400">1 maand van tevoren</p> <p class="text-xs font-bold tracking-wide text-amber-700 uppercase dark:text-amber-400">{{ __('1 maand van tevoren') }}</p>
<span class="rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold text-amber-700 dark:bg-amber-500/15 dark:text-amber-400">Herinnering</span> <span class="rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-semibold text-amber-700 dark:bg-amber-500/15 dark:text-amber-400">{{ __('Herinnering') }}</span>
</div> </div>
<p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">Nog geen nieuw certificaat ge&uuml;pload</p> <p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">{{ __('Nog geen nieuw certificaat geüpload') }}</p>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">Certified herinnert je opnieuw zolang het niet is verlengd.</p> <p class="mt-1 text-sm text-slate-500 dark:text-slate-400">{{ __('Certified herinnert je opnieuw zolang het niet is verlengd.') }}</p>
</div> </div>
</div> </div>
@ -328,11 +328,11 @@
</span> </span>
<div class="flex-1 rounded-2xl border border-red-200 bg-red-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-red-500/10 sm:p-5 dark:border-red-500/25 dark:bg-red-500/5"> <div class="flex-1 rounded-2xl border border-red-200 bg-red-50/60 p-4 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-red-500/10 sm:p-5 dark:border-red-500/25 dark:bg-red-500/5">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<p class="text-xs font-bold tracking-wide text-red-700 uppercase dark:text-red-400">Vandaag verlopen</p> <p class="text-xs font-bold tracking-wide text-red-700 uppercase dark:text-red-400">{{ __('Vandaag verlopen') }}</p>
<span class="rounded-full bg-red-100 px-2 py-0.5 text-[10px] font-semibold text-red-700 dark:bg-red-500/15 dark:text-red-400">Urgent</span> <span class="rounded-full bg-red-100 px-2 py-0.5 text-[10px] font-semibold text-red-700 dark:bg-red-500/15 dark:text-red-400">{{ __('Urgent') }}</span>
</div> </div>
<p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">Direct een urgente melding in je inbox</p> <p class="mt-2 text-sm font-semibold text-slate-800 dark:text-slate-200">{{ __('Direct een urgente melding in je inbox') }}</p>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">Zo weet je meteen welke medewerker niet meer inzetbaar is.</p> <p class="mt-1 text-sm text-slate-500 dark:text-slate-400">{{ __('Zo mis je nooit de deadline om het op tijd te verlengen.') }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -344,13 +344,13 @@
<div class="mx-auto max-w-2xl text-center"> <div class="mx-auto max-w-2xl text-center">
<span class="inline-flex items-center gap-2 rounded-full bg-teal-100 px-3 py-1 text-xs font-bold tracking-wide text-teal-700 uppercase dark:bg-teal-500/15 dark:text-teal-400"> <span class="inline-flex items-center gap-2 rounded-full bg-teal-100 px-3 py-1 text-xs font-bold tracking-wide text-teal-700 uppercase dark:bg-teal-500/15 dark:text-teal-400">
<svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" /></svg> <svg class="size-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" /></svg>
Secure sharing {{ __('Veilig delen') }}
</span> </span>
<h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white"> <h3 class="mt-5 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
Stop met certificaten mailen als losse bijlage {{ __('Stop met certificaten mailen als losse bijlage') }}
</h3> </h3>
<p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400"> <p class="mt-4 text-lg leading-relaxed text-slate-600 dark:text-slate-400">
Een VOG met BSN als onbeveiligde bijlage rondsturen is een datalek dat wacht om te gebeuren. Certified deelt documenten alleen versleuteld en tijdelijk. {{ __('Je VOG of diploma als onbeveiligde bijlage naar een nieuwe werkgever of instantie mailen is vragen om problemen. Certified deelt je documenten alleen versleuteld en tijdelijk.') }}
</p> </p>
</div> </div>
@ -363,24 +363,24 @@
<span class="flex size-10 items-center justify-center rounded-xl bg-red-100 dark:bg-red-500/15"> <span class="flex size-10 items-center justify-center rounded-xl bg-red-100 dark:bg-red-500/15">
<svg class="size-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" /></svg> <svg class="size-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" /></svg>
</span> </span>
<h4 class="text-lg font-bold text-slate-900 dark:text-white">E-mailbijlage <span class="font-medium text-red-600 dark:text-red-400">(oude manier)</span></h4> <h4 class="text-lg font-bold text-slate-900 dark:text-white">{{ __('E-mailbijlage') }} <span class="font-medium text-red-600 dark:text-red-400">({{ __('oude manier') }})</span></h4>
</div> </div>
{{-- Mock email --}} {{-- Mock email --}}
<div class="mt-6 rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800" aria-hidden="true"> <div class="mt-6 rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-800" aria-hidden="true">
<p class="text-xs text-slate-400">Aan: opdrachtgever@extern.nl</p> <p class="text-xs text-slate-400">{{ __('Aan') }}: hr@nieuwewerkgever.nl</p>
<p class="mt-1 text-sm font-semibold text-slate-700 dark:text-slate-200">FW: FW: VOG Jan zie bijlage</p> <p class="mt-1 text-sm font-semibold text-slate-700 dark:text-slate-200">FW: FW: {{ __('Mijn VOG — zie bijlage') }}</p>
<div class="mt-3 inline-flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 px-3 py-1.5 dark:border-red-500/25 dark:bg-red-500/10"> <div class="mt-3 inline-flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 px-3 py-1.5 dark:border-red-500/25 dark:bg-red-500/10">
<svg class="size-4 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13" /></svg> <svg class="size-4 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13" /></svg>
<span class="text-xs font-medium text-red-700 dark:text-red-400">vog-jan-BSN.pdf</span> <span class="text-xs font-medium text-red-700 dark:text-red-400">vog.pdf</span>
</div> </div>
</div> </div>
<ul class="mt-6 space-y-3 text-sm"> <ul class="mt-6 space-y-3 text-sm">
@foreach ([ @foreach ([
'Bijlage blijft voor altijd in inboxen en op mailservers staan', __('Bijlage blijft voor altijd in inboxen en op mailservers staan'),
'Geen controle: doorsturen, kopi&euml;ren en lekken kan ongemerkt', __('Geen controle: doorsturen, kopiëren en lekken kan ongemerkt'),
'BSN onversleuteld verstuurd — een meldplichtig datalek onder de AVG', __('Onbeveiligd verstuurd — een onnodig risico op misbruik van je gegevens'),
] as $risk) ] as $risk)
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<svg class="mt-0.5 size-4.5 shrink-0 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg> <svg class="mt-0.5 size-4.5 shrink-0 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
@ -396,7 +396,7 @@
<span class="flex size-10 items-center justify-center rounded-xl bg-emerald-100 dark:bg-emerald-500/15"> <span class="flex size-10 items-center justify-center rounded-xl bg-emerald-100 dark:bg-emerald-500/15">
<svg class="size-5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" /></svg> <svg class="size-5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" /></svg>
</span> </span>
<h4 class="text-lg font-bold text-slate-900 dark:text-white">Delen met <span class="text-emerald-600 dark:text-emerald-400">Certified</span></h4> <h4 class="text-lg font-bold text-slate-900 dark:text-white">{{ __('Delen met') }} <span class="text-emerald-600 dark:text-emerald-400">Certified</span></h4>
</div> </div>
{{-- Mock share --}} {{-- Mock share --}}
@ -406,16 +406,16 @@
<span class="truncate font-mono text-xs text-slate-500 dark:text-slate-400">certified.eu/shared/?signature=a8f3</span> <span class="truncate font-mono text-xs text-slate-500 dark:text-slate-400">certified.eu/shared/?signature=a8f3</span>
</div> </div>
<div class="mt-3 flex flex-wrap items-center gap-2"> <div class="mt-3 flex flex-wrap items-center gap-2">
<span class="rounded-full bg-emerald-100 px-2.5 py-1 text-[10px] font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">🔒 AES-256 versleuteld</span> <span class="rounded-full bg-emerald-100 px-2.5 py-1 text-[10px] font-semibold text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400">🔒 {{ __('AES-256 versleuteld') }}</span>
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[10px] font-semibold text-slate-600 dark:bg-slate-700 dark:text-slate-300"> Verloopt over 48 uur</span> <span class="rounded-full bg-slate-100 px-2.5 py-1 text-[10px] font-semibold text-slate-600 dark:bg-slate-700 dark:text-slate-300"> {{ __('Verloopt over 48 uur') }}</span>
</div> </div>
</div> </div>
<ul class="mt-6 space-y-3 text-sm"> <ul class="mt-6 space-y-3 text-sm">
@foreach ([ @foreach ([
'AES-256 versleutelde ZIP met een apart verstuurd wachtwoord', __('AES-256 versleutelde ZIP met een apart verstuurd wachtwoord'),
'Tijdelijke ondertekende URL die na 48 uur automatisch verloopt', __('Tijdelijke ondertekende URL die na 48 uur automatisch verloopt'),
'Volledig AVG-proof: geen kopie&euml;n die blijven rondslingeren', __('Privacyvriendelijk: geen kopieën die ergens blijven rondslingeren'),
] as $benefit) ] as $benefit)
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<svg class="mt-0.5 size-4.5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg> <svg class="mt-0.5 size-4.5 shrink-0 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>
@ -436,12 +436,12 @@
<div class="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div class="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl text-center"> <div class="mx-auto max-w-2xl text-center">
<p class="text-sm font-semibold tracking-widest text-emerald-400 uppercase">Europese privacy &amp; security</p> <p class="text-sm font-semibold tracking-widest text-emerald-400 uppercase">{{ __('Europese privacy & security') }}</p>
<h2 class="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl"> <h2 class="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl">
Gevoelige gegevens verdienen Europese waarborgen {{ __('Gevoelige gegevens verdienen Europese waarborgen') }}
</h2> </h2>
<p class="mt-4 text-lg leading-relaxed text-slate-400"> <p class="mt-4 text-lg leading-relaxed text-slate-400">
VOG's en BSN-nummers zijn bijzondere persoonsgegevens. Daarom is Certified vanaf de eerste regel code gebouwd op privacy-by-design en blijft je data binnen de EU. {{ __("Documenten zoals een VOG bevatten bijzondere persoonsgegevens. Daarom is Certified vanaf de eerste regel code gebouwd op privacy-by-design — en blijft je data binnen de EU.") }}
</p> </p>
</div> </div>
@ -449,18 +449,18 @@
@foreach ([ @foreach ([
[ [
'icon' => 'M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418', 'icon' => 'M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418',
'title' => '100% Europese hosting', 'title' => __('100% Europese hosting'),
'body' => 'Alle data staat in Europese datacenters, onder Europees recht. Geen Amerikaanse cloud, geen CLOUD Act, volledig AVG/GDPR-compliant.', 'body' => __('Al je data staat in Europese datacenters, onder Europees recht. Geen Amerikaanse cloud, geen CLOUD Act, volledig AVG/GDPR-compliant.'),
], ],
[ [
'icon' => 'M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z', 'icon' => 'M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z',
'title' => 'AES-256 versleuteld delen', 'title' => __('AES-256 versleuteld delen'),
'body' => 'Gedeelde certificaten worden verpakt in een AES-256 versleutelde ZIP. Links zijn ondertekend en verlopen automatisch na 48 uur.', 'body' => __('Certificaten die je deelt worden verpakt in een AES-256 versleutelde ZIP. Links zijn ondertekend en verlopen automatisch na 48 uur.'),
], ],
[ [
'icon' => 'M7.5 3.75H6A2.25 2.25 0 0 0 3.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0 1 20.25 6v1.5m0 9V18A2.25 2.25 0 0 1 18 20.25h-1.5m-9 0H6A2.25 2.25 0 0 1 3.75 18v-1.5M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z', 'icon' => 'M7.5 3.75H6A2.25 2.25 0 0 0 3.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0 1 20.25 6v1.5m0 9V18A2.25 2.25 0 0 1 18 20.25h-1.5m-9 0H6A2.25 2.25 0 0 1 3.75 18v-1.5M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z',
'title' => 'Europese OCR-providers', 'title' => __('Europese OCR-providers'),
'body' => 'Kies zelf wie je documenten leest: Klippa (Nederland) of Mistral AI (Frankrijk). Je documenten verlaten Europa nooit — jij houdt de regie.', 'body' => __('Kies zelf wie je documenten leest: Klippa (Nederland) of Mistral AI (Frankrijk). Je documenten verlaten Europa nooit — jij houdt de regie.'),
], ],
] as $card) ] as $card)
<div class="group rounded-2xl border border-slate-700/80 bg-slate-800/50 p-7 backdrop-blur transition-all duration-300 hover:-translate-y-1 hover:border-emerald-500/40 hover:bg-slate-800/80 hover:shadow-xl hover:shadow-emerald-500/5"> <div class="group rounded-2xl border border-slate-700/80 bg-slate-800/50 p-7 backdrop-blur transition-all duration-300 hover:-translate-y-1 hover:border-emerald-500/40 hover:bg-slate-800/80 hover:shadow-xl hover:shadow-emerald-500/5">
@ -478,7 +478,7 @@
href="{{ route('marketing.security') }}" href="{{ route('marketing.security') }}"
class="group inline-flex items-center gap-2 text-base font-semibold text-emerald-400 transition-colors hover:text-emerald-300" class="group inline-flex items-center gap-2 text-base font-semibold text-emerald-400 transition-colors hover:text-emerald-300"
> >
Lees alles over security &amp; privacy {{ __('Lees alles over security & privacy') }}
<span class="transition-transform duration-200 group-hover:translate-x-1" aria-hidden="true">&rarr;</span> <span class="transition-transform duration-200 group-hover:translate-x-1" aria-hidden="true">&rarr;</span>
</a> </a>
</div> </div>
@ -486,15 +486,15 @@
</section> </section>
{{-- ============ PRICING ============ --}} {{-- ============ PRICING ============ --}}
<section class="py-20 sm:py-28" aria-label="Prijzen"> <section class="py-20 sm:py-28" aria-label="{{ __('Prijzen') }}">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="mx-auto mb-14 max-w-2xl text-center"> <div class="mx-auto mb-14 max-w-2xl text-center">
<p class="text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">Pricing</p> <p class="text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">Pricing</p>
<h2 class="mt-3 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white"> <h2 class="mt-3 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Eerlijke prijzen, geen verrassingen {{ __('Eerlijke prijzen, geen verrassingen') }}
</h2> </h2>
<p class="mt-4 text-lg text-slate-500 dark:text-slate-400"> <p class="mt-4 text-lg text-slate-500 dark:text-slate-400">
Start gratis en groei mee wanneer je meer certificaten beheert. Elk pakket bevat OCR, reminders en veilig delen. {{ __('Start gratis en groei mee wanneer je meer certificaten beheert. Elk pakket bevat OCR, reminders en veilig delen.') }}
</p> </p>
</div> </div>
@ -504,12 +504,12 @@
{{-- ============ FAQ ============ --}} {{-- ============ FAQ ============ --}}
@include('marketing.partials.faq', ['faqs' => [ @include('marketing.partials.faq', ['faqs' => [
['q' => 'Is Certified geschikt voor documenten met BSN-nummers, zoals een VOG?', 'a' => 'Ja, daar is Certified juist voor gebouwd. Documenten staan versleuteld op Europese servers, worden nooit als open bijlage gedeeld en zijn alleen toegankelijk voor geautoriseerde gebruikers. Delen gebeurt via een tijdelijke ondertekende link of een AES-256 versleutelde ZIP.'], ['q' => __('Is Certified geschikt voor gevoelige documenten, zoals een VOG?'), 'a' => __('Ja, daar is Certified juist voor gebouwd. Je documenten staan versleuteld op Europese servers, worden nooit als open bijlage gedeeld en zijn alleen toegankelijk met jouw account. Delen met anderen gebeurt via een tijdelijke ondertekende link of een AES-256 versleutelde ZIP.')],
['q' => 'Hoe werkt de automatische OCR-scan?', 'a' => 'Je uploadt een PDF of foto van het certificaat. Onze OCR-engine herkent automatisch de titel, uitgever en vervaldatum en vult die voor je in. Jij controleert en slaat op — dat is alles. Je kiest zelf welke Europese provider (Klippa of Mistral AI) de verwerking doet.'], ['q' => __('Hoe werkt de automatische OCR-scan?'), 'a' => __('Je uploadt een PDF of foto van je certificaat. Onze OCR-engine herkent automatisch de titel, uitgever en vervaldatum en vult die voor je in. Jij controleert en slaat op — dat is alles. Je kiest zelf welke Europese provider (Klippa of Mistral AI) de verwerking doet.')],
['q' => 'Wanneer krijg ik een reminder als een certificaat bijna verloopt?', 'a' => 'Standaard ontvang je twee maanden van tevoren een e-mail, en daarna opnieuw zolang het certificaat niet verlengd is. Verloopt een certificaat vandaag, dan krijg je direct een urgente melding. Het aantal maanden stel je zelf in per account.'], ['q' => __('Wanneer krijg ik een reminder als een certificaat bijna verloopt?'), 'a' => __('Standaard ontvang je twee maanden van tevoren een e-mail, en daarna opnieuw zolang je het certificaat niet hebt verlengd. Verloopt een certificaat vandaag, dan krijg je direct een urgente melding. Het aantal maanden stel je zelf in bij je instellingen.')],
['q' => 'Waar wordt mijn data opgeslagen?', 'a' => 'Uitsluitend in Europese datacenters, onder Europees recht. We gebruiken geen Amerikaanse cloudproviders, waardoor je data niet onder de Amerikaanse CLOUD Act valt. Zo voldoe je aantoonbaar aan de AVG.'], ['q' => __('Waar wordt mijn data opgeslagen?'), 'a' => __('Uitsluitend in Europese datacenters, onder Europees recht. We gebruiken geen Amerikaanse cloudproviders, waardoor je data niet onder de Amerikaanse CLOUD Act valt. Zo voldoe je aantoonbaar aan de AVG.')],
['q' => 'Kan ik Certified eerst vrijblijvend proberen?', 'a' => 'Zeker. Het Gratis-pakket is voor altijd gratis en laat je tot 5 certificaten beheren — zonder creditcard. Groei je eruit, dan upgrade je eenvoudig naar Starter (20 certificaten) of Onbeperkt.'], ['q' => __('Kan ik Certified eerst vrijblijvend proberen?'), 'a' => __('Zeker. Het Gratis-pakket is voor altijd gratis en laat je tot 5 certificaten beheren — zonder creditcard. Groei je eruit, dan upgrade je eenvoudig naar Starter (20 certificaten) of Onbeperkt.')],
['q' => 'Sluiten jullie een verwerkersovereenkomst af?', 'a' => 'Ja. Voor alle betaalde abonnementen bieden we een standaard verwerkersovereenkomst aan, inclusief afspraken over dataretentie en de door jou gekozen OCR-provider.'], ['q' => __('Heb ik een verwerkersovereenkomst nodig?'), 'a' => __('Gebruik je Certified puur voor je eigen certificaten? Dan heb je geen verwerkersovereenkomst nodig — je bent geen verwerkingsverantwoordelijke in de zin van de AVG. Werk je als zzp\'er of eenmanszaak en wil je er toch één voor je eigen administratie, dan kun je die downloaden op onze DPA-pagina.')],
]]) ]])
{{-- ============ BOTTOM CTA ============ --}} {{-- ============ BOTTOM CTA ============ --}}

View file

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="nl" class="scroll-smooth"> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="scroll-smooth">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', 'Certified — Nooit meer verlopen certificaten')</title> <title>@yield('title', 'Certified — '.__('Al je certificaten op één plek. Nooit meer verlopen.'))</title>
<meta name="description" content="@yield('description', 'Certified bewaakt de geldigheid van al je certificaten. Automatische OCR, slimme reminders en AVG-proof delen — 100% Europese hosting.')"> <meta name="description" content="@yield('description', __('Certified bewaakt de geldigheid van al je certificaten. Automatische OCR, slimme reminders en AVG-proof delen — 100% Europese hosting.'))">
<link rel="icon" href="/favicon.ico" sizes="any"> <link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.svg" type="image/svg+xml"> <link rel="icon" href="/favicon.svg" type="image/svg+xml">

View file

@ -1,26 +1,26 @@
<section class="px-4 pb-20 sm:px-6 sm:pb-24" aria-label="Aan de slag"> <section class="px-4 pb-20 sm:px-6 sm:pb-24" aria-label="{{ __('Aan de slag') }}">
<div class="relative mx-auto max-w-6xl overflow-hidden rounded-3xl bg-gradient-to-br from-emerald-600 via-emerald-700 to-teal-800 px-6 py-16 text-center shadow-2xl shadow-emerald-900/30 sm:px-16 sm:py-20"> <div class="relative mx-auto max-w-6xl overflow-hidden rounded-3xl bg-gradient-to-br from-emerald-600 via-emerald-700 to-teal-800 px-6 py-16 text-center shadow-2xl shadow-emerald-900/30 sm:px-16 sm:py-20">
<div class="pointer-events-none absolute inset-0 bg-grid-slate opacity-20" aria-hidden="true"></div> <div class="pointer-events-none absolute inset-0 bg-grid-slate opacity-20" aria-hidden="true"></div>
<div class="pointer-events-none absolute -top-24 left-1/2 size-96 -translate-x-1/2 rounded-full bg-emerald-400/30 blur-3xl" aria-hidden="true"></div> <div class="pointer-events-none absolute -top-24 left-1/2 size-96 -translate-x-1/2 rounded-full bg-emerald-400/30 blur-3xl" aria-hidden="true"></div>
<div class="relative"> <div class="relative">
<h2 class="mx-auto max-w-2xl text-3xl font-bold tracking-tight text-white sm:text-4xl"> <h2 class="mx-auto max-w-2xl text-3xl font-bold tracking-tight text-white sm:text-4xl">
Regel je compliance vandaag nog. Niet pas na de audit. {{ __('Regel het vandaag nog. Niet pas als het te laat is.') }}
</h2> </h2>
<p class="mx-auto mt-4 max-w-xl text-lg leading-relaxed text-emerald-100"> <p class="mx-auto mt-4 max-w-xl text-lg leading-relaxed text-emerald-100">
Binnen tien minuten staan je eerste certificaten in Certified inclusief automatische reminders. Start gratis met maximaal 5 certificaten. {{ __('Binnen tien minuten staan je eerste certificaten in Certified — inclusief automatische reminders. Start gratis met maximaal 5 certificaten.') }}
</p> </p>
<div class="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row"> <div class="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<a <a
href="{{ route('register') }}" href="{{ route('register') }}"
class="w-full rounded-xl bg-white px-8 py-3.5 text-base font-semibold text-emerald-800 shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:bg-emerald-50 hover:shadow-xl active:translate-y-0 sm:w-auto" class="w-full rounded-xl bg-white px-8 py-3.5 text-base font-semibold text-emerald-800 shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:bg-emerald-50 hover:shadow-xl active:translate-y-0 sm:w-auto"
>Vraag toegang aan</a> >{{ __('Start gratis') }}</a>
<a <a
href="{{ route('marketing.pricing') }}" href="{{ route('marketing.pricing') }}"
class="w-full rounded-xl border border-emerald-300/40 px-8 py-3.5 text-base font-semibold text-white transition-colors duration-200 hover:border-emerald-200 hover:bg-white/10 sm:w-auto" class="w-full rounded-xl border border-emerald-300/40 px-8 py-3.5 text-base font-semibold text-white transition-colors duration-200 hover:border-emerald-200 hover:bg-white/10 sm:w-auto"
>Bekijk de prijzen</a> >{{ __('Bekijk de prijzen') }}</a>
</div> </div>
<p class="mt-6 text-sm text-emerald-200/80">Gratis tot 5 certificaten &middot; Geen creditcard nodig &middot; Opzeggen wanneer je wilt</p> <p class="mt-6 text-sm text-emerald-200/80">{{ __('Gratis tot 5 certificaten') }} &middot; {{ __('Geen creditcard nodig') }} &middot; {{ __('Opzeggen wanneer je wilt') }}</p>
</div> </div>
</div> </div>
</section> </section>

View file

@ -1,11 +1,11 @@
{{-- Expects: $faqs (array of ['q' => ..., 'a' => ...]), optional $faqTitle / $faqSubtitle --}} {{-- Expects: $faqs (array of ['q' => ..., 'a' => ...]), optional $faqTitle / $faqSubtitle --}}
<section class="mx-auto max-w-3xl px-4 py-20 sm:px-6 sm:py-24" aria-label="Veelgestelde vragen"> <section class="mx-auto max-w-3xl px-4 py-20 sm:px-6 sm:py-24" aria-label="{{ __('Veelgestelde vragen') }}">
<div class="text-center"> <div class="text-center">
<h2 class="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white"> <h2 class="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
{{ $faqTitle ?? 'Veelgestelde vragen' }} {{ $faqTitle ?? __('Veelgestelde vragen') }}
</h2> </h2>
<p class="mt-4 text-lg text-slate-500 dark:text-slate-400"> <p class="mt-4 text-lg text-slate-500 dark:text-slate-400">
{{ $faqSubtitle ?? 'Staat je vraag er niet tussen? Mail ons via support@certified.eu.' }} {{ $faqSubtitle ?? __('Staat je vraag er niet tussen? Mail ons via support@certified.eu.') }}
</p> </p>
</div> </div>

View file

@ -7,14 +7,14 @@
<span class="text-base font-semibold tracking-tight text-slate-900 dark:text-white">Certified</span> <span class="text-base font-semibold tracking-tight text-slate-900 dark:text-white">Certified</span>
</a> </a>
<p class="mt-4 max-w-sm text-sm leading-relaxed text-slate-500 dark:text-slate-400"> <p class="mt-4 max-w-sm text-sm leading-relaxed text-slate-500 dark:text-slate-400">
De veilige certificaatmanager voor organisaties die met gevoelige persoonsgegevens werken. Gebouwd en gehost in Europa. {{ __("De veilige plek waar je zelf al je certificaten, diploma's en VOG's beheert. Gebouwd en gehost in Europa.") }}
</p> </p>
<div class="mt-5 flex items-center gap-2 text-xs font-medium text-slate-500 dark:text-slate-400"> <div class="mt-5 flex items-center gap-2 text-xs font-medium text-slate-500 dark:text-slate-400">
<span class="inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 dark:border-slate-700 dark:bg-slate-900"> <span class="inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 dark:border-slate-700 dark:bg-slate-900">
<span class="size-1.5 rounded-full bg-emerald-500"></span> AVG / GDPR-proof <span class="size-1.5 rounded-full bg-emerald-500"></span> {{ __('AVG / GDPR-proof') }}
</span> </span>
<span class="inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 dark:border-slate-700 dark:bg-slate-900"> <span class="inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 dark:border-slate-700 dark:bg-slate-900">
🇪🇺 100% Europese hosting 🇪🇺 {{ __('100% Europese hosting') }}
</span> </span>
</div> </div>
</div> </div>
@ -22,26 +22,27 @@
<nav aria-label="Product"> <nav aria-label="Product">
<h3 class="text-sm font-semibold text-slate-900 dark:text-white">Product</h3> <h3 class="text-sm font-semibold text-slate-900 dark:text-white">Product</h3>
<ul class="mt-4 space-y-3 text-sm"> <ul class="mt-4 space-y-3 text-sm">
<li><a href="{{ route('home') }}#features" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Features</a></li> <li><a href="{{ route('home') }}#features" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Features') }}</a></li>
<li><a href="{{ route('marketing.security') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Security &amp; privacy</a></li> <li><a href="{{ route('marketing.security') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Security & privacy') }}</a></li>
<li><a href="{{ route('marketing.pricing') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Pricing</a></li> <li><a href="{{ route('marketing.pricing') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Pricing</a></li>
<li><a href="{{ route('login') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Login</a></li> <li><a href="{{ route('verify.show') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Verify a certificate') }}</a></li>
<li><a href="{{ route('login') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Login') }}</a></li>
</ul> </ul>
</nav> </nav>
<nav aria-label="Juridisch"> <nav aria-label="{{ __('Juridisch') }}">
<h3 class="text-sm font-semibold text-slate-900 dark:text-white">Juridisch</h3> <h3 class="text-sm font-semibold text-slate-900 dark:text-white">{{ __('Juridisch') }}</h3>
<ul class="mt-4 space-y-3 text-sm"> <ul class="mt-4 space-y-3 text-sm">
<li><a href="#" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Privacyverklaring</a></li> <li><a href="{{ route('marketing.privacy') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Privacyverklaring') }}</a></li>
<li><a href="#" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Verwerkersovereenkomst</a></li> <li><a href="{{ route('marketing.dpa') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Verwerkersovereenkomst') }}</a></li>
<li><a href="#" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">Algemene voorwaarden</a></li> <li><a href="{{ route('marketing.terms') }}" class="text-slate-500 transition-colors hover:text-emerald-600 dark:text-slate-400 dark:hover:text-emerald-400">{{ __('Algemene voorwaarden') }}</a></li>
</ul> </ul>
</nav> </nav>
</div> </div>
<div class="mt-12 flex flex-col items-center justify-between gap-4 border-t border-slate-200 pt-8 sm:flex-row dark:border-slate-800"> <div class="mt-12 flex flex-col items-center justify-between gap-4 border-t border-slate-200 pt-8 sm:flex-row dark:border-slate-800">
<p class="text-sm text-slate-500 dark:text-slate-400">&copy; {{ date('Y') }} Certified. Alle rechten voorbehouden.</p> <p class="text-sm text-slate-500 dark:text-slate-400">&copy; {{ date('Y') }} Certified. {{ __('Alle rechten voorbehouden.') }}</p>
<p class="text-sm text-slate-400 dark:text-slate-500">Gemaakt in Nederland 🇳🇱</p> <p class="text-sm text-slate-400 dark:text-slate-500">{{ __('Gemaakt in Nederland') }} 🇳🇱</p>
</div> </div>
</div> </div>
</footer> </footer>

View file

@ -0,0 +1,16 @@
{{-- Expects: $title, $updated (version/date line, already fully composed) --}}
<section class="relative overflow-hidden">
<div class="pointer-events-none absolute inset-0 bg-grid-slate [mask-image:radial-gradient(ellipse_70%_60%_at_50%_0%,black,transparent)]" aria-hidden="true"></div>
<div class="relative mx-auto max-w-3xl px-4 pt-36 pb-12 text-center sm:px-6 sm:pt-44 sm:pb-16">
<p class="animate-fade-in-up inline-flex items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-4 py-1.5 text-sm font-medium text-emerald-700 dark:border-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400">
🇪🇺 {{ __('Nederlands recht, EU-hosting') }}
</p>
<h1 class="animate-fade-in-up mt-8 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-5xl dark:text-white">
{{ $title }}
</h1>
<p class="animate-fade-in-up mt-5 text-sm text-slate-500 [animation-delay:200ms] dark:text-slate-400">
{{ $updated }}
</p>
</div>
</section>

View file

@ -0,0 +1,13 @@
{{-- Expects: $label (e.g. 'AANBIEDER'), optional $email --}}
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-5 dark:border-slate-800 dark:bg-slate-900/60">
<p class="text-xs font-bold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">{{ $label }}</p>
<p class="mt-2 text-base font-semibold text-slate-900 dark:text-white">Bckstage</p>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
Mortelweg 10D, 6525AJ Weurt<br>
{{ __('Nederland') }}
</p>
<p class="mt-2 text-sm text-slate-500 dark:text-slate-400">KvK 54620910 &middot; BTW NL002260150B11</p>
<p class="mt-2 text-sm">
<a href="mailto:{{ $email ?? 'privacy@bckstage.cloud' }}" class="font-medium text-emerald-700 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300">{{ $email ?? 'privacy@bckstage.cloud' }}</a>
</p>
</div>

View file

@ -8,12 +8,12 @@
:class="scrolled :class="scrolled
? 'border-slate-200/80 bg-white/80 shadow-sm shadow-slate-900/5 dark:border-slate-800 dark:bg-slate-950/80' ? 'border-slate-200/80 bg-white/80 shadow-sm shadow-slate-900/5 dark:border-slate-800 dark:bg-slate-950/80'
: 'border-transparent bg-white/40 dark:bg-slate-950/40'" : 'border-transparent bg-white/40 dark:bg-slate-950/40'"
aria-label="Hoofdnavigatie" aria-label="{{ __('Hoofdnavigatie') }}"
> >
<div class="mx-auto flex h-16 max-w-7xl items-center justify-between gap-6 px-4 sm:px-6 lg:px-8"> <div class="mx-auto flex h-16 max-w-7xl items-center justify-between gap-6 px-4 sm:px-6 lg:px-8">
{{-- Logo --}} {{-- Logo --}}
<a href="{{ route('home') }}" class="group flex items-center gap-2.5" aria-label="Certified — home"> <a href="{{ route('home') }}" class="group flex items-center gap-2.5" aria-label="{{ __('Certified — home') }}">
<x-brand-mark class="transition-transform duration-300 group-hover:scale-105 group-hover:-rotate-3" /> <x-brand-mark class="transition-transform duration-300 group-hover:scale-105 group-hover:-rotate-3" />
<span class="text-lg font-semibold tracking-tight text-slate-900 dark:text-white">Certified</span> <span class="text-lg font-semibold tracking-tight text-slate-900 dark:text-white">Certified</span>
</a> </a>
@ -21,9 +21,10 @@
{{-- Desktop links --}} {{-- Desktop links --}}
<div class="hidden items-center gap-1 md:flex"> <div class="hidden items-center gap-1 md:flex">
@foreach ([ @foreach ([
['label' => 'Features', 'href' => route('home').'#features'], ['label' => __('Features'), 'href' => route('home').'#features'],
['label' => 'Security', 'href' => route('marketing.security')], ['label' => __('Security'), 'href' => route('marketing.security')],
['label' => 'Pricing', 'href' => route('marketing.pricing')], ['label' => __('Pricing'), 'href' => route('marketing.pricing')],
['label' => __('Verify a certificate'), 'href' => route('verify.show')],
] as $link) ] as $link)
<a <a
href="{{ $link['href'] }}" href="{{ $link['href'] }}"
@ -41,7 +42,7 @@
localStorage.setItem('flux.appearance', dark ? 'dark' : 'light'); localStorage.setItem('flux.appearance', dark ? 'dark' : 'light');
" "
class="flex size-9 items-center justify-center rounded-lg text-slate-500 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800/60 dark:hover:text-white" class="flex size-9 items-center justify-center rounded-lg text-slate-500 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800/60 dark:hover:text-white"
aria-label="Wissel tussen licht en donker thema" aria-label="{{ __('Wissel tussen licht en donker thema') }}"
> >
<svg class="size-5 dark:hidden" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true"> <svg class="size-5 dark:hidden" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
@ -51,15 +52,29 @@
</svg> </svg>
</button> </button>
{{-- Language switcher --}}
<div class="hidden items-center gap-1 rounded-lg border border-slate-200 p-0.5 sm:flex dark:border-slate-700">
@foreach (\App\Enums\Locale::cases() as $locale)
<a
href="{{ route('language.switch', $locale->value) }}"
@class([
'rounded-md px-2 py-1 text-xs font-semibold transition-colors',
'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400' => app()->getLocale() === $locale->value,
'text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white' => app()->getLocale() !== $locale->value,
])
>{{ strtoupper($locale->value) }}</a>
@endforeach
</div>
<a <a
href="{{ route('login') }}" href="{{ route('login') }}"
class="hidden rounded-lg px-3.5 py-2 text-sm font-medium text-slate-600 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 sm:block dark:text-slate-400 dark:hover:bg-slate-800/60 dark:hover:text-white" class="hidden rounded-lg px-3.5 py-2 text-sm font-medium text-slate-600 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-900 sm:block dark:text-slate-400 dark:hover:bg-slate-800/60 dark:hover:text-white"
>Login</a> >{{ __('Login') }}</a>
<a <a
href="{{ route('register') }}" href="{{ route('register') }}"
class="hidden rounded-lg bg-emerald-600 px-4 py-2 text-sm font-semibold text-white shadow-md shadow-emerald-600/25 transition-all duration-200 hover:-translate-y-px hover:bg-emerald-500 hover:shadow-lg hover:shadow-emerald-500/30 active:translate-y-0 sm:block" class="hidden rounded-lg bg-emerald-600 px-4 py-2 text-sm font-semibold text-white shadow-md shadow-emerald-600/25 transition-all duration-200 hover:-translate-y-px hover:bg-emerald-500 hover:shadow-lg hover:shadow-emerald-500/30 active:translate-y-0 sm:block"
>Vraag toegang aan</a> >{{ __('Start gratis') }}</a>
{{-- Mobile hamburger --}} {{-- Mobile hamburger --}}
<button <button
@ -67,7 +82,7 @@
@click="open = !open" @click="open = !open"
class="flex size-9 items-center justify-center rounded-lg text-slate-600 transition-colors hover:bg-slate-100 md:hidden dark:text-slate-400 dark:hover:bg-slate-800/60" class="flex size-9 items-center justify-center rounded-lg text-slate-600 transition-colors hover:bg-slate-100 md:hidden dark:text-slate-400 dark:hover:bg-slate-800/60"
:aria-expanded="open" :aria-expanded="open"
aria-label="Menu openen" aria-label="{{ __('Menu openen') }}"
> >
<svg x-show="!open" class="size-6" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true"> <svg x-show="!open" class="size-6" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
@ -93,10 +108,11 @@
class="border-t border-slate-200/80 bg-white/95 px-4 pt-2 pb-4 backdrop-blur-xl md:hidden dark:border-slate-800 dark:bg-slate-950/95" class="border-t border-slate-200/80 bg-white/95 px-4 pt-2 pb-4 backdrop-blur-xl md:hidden dark:border-slate-800 dark:bg-slate-950/95"
> >
@foreach ([ @foreach ([
['label' => 'Features', 'href' => route('home').'#features'], ['label' => __('Features'), 'href' => route('home').'#features'],
['label' => 'Security', 'href' => route('marketing.security')], ['label' => __('Security'), 'href' => route('marketing.security')],
['label' => 'Pricing', 'href' => route('marketing.pricing')], ['label' => __('Pricing'), 'href' => route('marketing.pricing')],
['label' => 'Login', 'href' => route('login')], ['label' => __('Verify a certificate'), 'href' => route('verify.show')],
['label' => __('Login'), 'href' => route('login')],
] as $link) ] as $link)
<a <a
href="{{ $link['href'] }}" href="{{ $link['href'] }}"
@ -105,10 +121,23 @@
>{{ $link['label'] }}</a> >{{ $link['label'] }}</a>
@endforeach @endforeach
<div class="mt-2 flex items-center gap-2 px-3">
@foreach (\App\Enums\Locale::cases() as $locale)
<a
href="{{ route('language.switch', $locale->value) }}"
@class([
'rounded-lg border px-3 py-1.5 text-sm font-medium transition-colors',
'border-emerald-500 bg-emerald-50 text-emerald-700 dark:border-emerald-500/40 dark:bg-emerald-500/10 dark:text-emerald-400' => app()->getLocale() === $locale->value,
'border-slate-200 text-slate-600 hover:border-slate-300 dark:border-slate-700 dark:text-slate-400' => app()->getLocale() !== $locale->value,
])
>{{ $locale->label() }}</a>
@endforeach
</div>
<a <a
href="{{ route('register') }}" href="{{ route('register') }}"
class="mt-3 block rounded-lg bg-emerald-600 px-4 py-2.5 text-center text-base font-semibold text-white shadow-md shadow-emerald-600/25 transition-colors hover:bg-emerald-500" class="mt-3 block rounded-lg bg-emerald-600 px-4 py-2.5 text-center text-base font-semibold text-white shadow-md shadow-emerald-600/25 transition-colors hover:bg-emerald-500"
>Vraag toegang aan</a> >{{ __('Start gratis') }}</a>
</div> </div>
</nav> </nav>
</header> </header>

View file

@ -2,48 +2,48 @@
<div class="grid gap-8 lg:grid-cols-3 lg:gap-6"> <div class="grid gap-8 lg:grid-cols-3 lg:gap-6">
@foreach ([ @foreach ([
[ [
'name' => 'Gratis', 'name' => __('Gratis'),
'description' => 'Voor wie Certified wil proberen of maar een handvol certificaten beheert.', 'description' => __('Voor wie Certified wil proberen of maar een handvol certificaten beheert.'),
'price' => '0', 'price' => '0',
'priceNote' => 'Voor altijd gratis, geen creditcard nodig', 'priceNote' => __('Voor altijd gratis, geen creditcard nodig'),
'featured' => false, 'featured' => false,
'cta' => 'Start gratis', 'cta' => __('Start gratis'),
'features' => [ 'features' => [
'Maximaal 5 certificaten', __('Maximaal 5 certificaten'),
'Automatische OCR-scan', __('Automatische OCR-scan'),
'Slimme e-mailreminders', __('Slimme e-mailreminders'),
'Veilig delen via signed URL', __('Veilig delen via signed URL'),
], ],
], ],
[ [
'name' => 'Starter', 'name' => 'Starter',
'description' => 'Voor zzp\'ers en kleine praktijken met een groeiende certificaatmap.', 'description' => __("Voor wie meer dan een handvol eigen certificaten en diploma's bijhoudt."),
'price' => '12,50', 'price' => '12,50',
'priceNote' => 'Eén vast bedrag, jaarlijks gefactureerd', 'priceNote' => __('Eén vast bedrag, jaarlijks gefactureerd'),
'featured' => true, 'featured' => true,
'cta' => 'Start met Starter', 'cta' => __('Start met Starter'),
'features' => [ 'features' => [
'Maximaal 20 certificaten', __('Maximaal 20 certificaten'),
'Automatische OCR-scan', __('Automatische OCR-scan'),
'Slimme e-mailreminders', __('Slimme e-mailreminders'),
'Veilig delen via signed URL', __('Veilig delen via signed URL'),
'AES-256 versleutelde ZIP-export', __('AES-256 versleutelde ZIP-export'),
], ],
], ],
[ [
'name' => 'Onbeperkt', 'name' => __('Onbeperkt'),
'description' => 'Voor organisaties die al hun certificaten op één plek willen bewaken.', 'description' => __("Voor wie al zijn diploma's, VOG's en certificaten voorgoed op één plek wil bewaken."),
'price' => '45', 'price' => '45',
'priceNote' => 'Eén vast bedrag, jaarlijks gefactureerd', 'priceNote' => __('Eén vast bedrag, jaarlijks gefactureerd'),
'featured' => false, 'featured' => false,
'cta' => 'Start met Onbeperkt', 'cta' => __('Start met Onbeperkt'),
'features' => [ 'features' => [
'Onbeperkt certificaten', __('Onbeperkt certificaten'),
'Automatische OCR-scan', __('Automatische OCR-scan'),
'Slimme e-mailreminders', __('Slimme e-mailreminders'),
'Veilig delen via signed URL', __('Veilig delen via signed URL'),
'AES-256 versleutelde ZIP-export', __('AES-256 versleutelde ZIP-export'),
'Prioriteit support', __('Prioriteit support'),
], ],
], ],
] as $tier) ] as $tier)
@ -54,7 +54,7 @@
])> ])>
@if ($tier['featured']) @if ($tier['featured'])
<span class="absolute -top-4 left-1/2 -translate-x-1/2 rounded-full bg-gradient-to-r from-emerald-500 to-teal-500 px-4 py-1 text-xs font-bold tracking-wide text-white uppercase shadow-lg shadow-emerald-500/30"> <span class="absolute -top-4 left-1/2 -translate-x-1/2 rounded-full bg-gradient-to-r from-emerald-500 to-teal-500 px-4 py-1 text-xs font-bold tracking-wide text-white uppercase shadow-lg shadow-emerald-500/30">
Meest gekozen {{ __('Meest gekozen') }}
</span> </span>
@endif @endif
@ -63,7 +63,7 @@
<div class="mt-6 flex items-baseline gap-1.5"> <div class="mt-6 flex items-baseline gap-1.5">
<span class="text-4xl font-bold tracking-tight text-slate-900 dark:text-white">&euro;{{ $tier['price'] }}</span> <span class="text-4xl font-bold tracking-tight text-slate-900 dark:text-white">&euro;{{ $tier['price'] }}</span>
<span class="text-sm font-medium text-slate-500 dark:text-slate-400">/ jaar</span> <span class="text-sm font-medium text-slate-500 dark:text-slate-400">/ {{ __('jaar') }}</span>
</div> </div>
<p class="mt-1 text-xs text-slate-400 dark:text-slate-500">{{ $tier['priceNote'] }}</p> <p class="mt-1 text-xs text-slate-400 dark:text-slate-500">{{ $tier['priceNote'] }}</p>
@ -91,6 +91,6 @@
</div> </div>
<p class="mt-10 text-center text-sm text-slate-500 dark:text-slate-400"> <p class="mt-10 text-center text-sm text-slate-500 dark:text-slate-400">
Alle prijzen zijn per jaar en exclusief btw. Start gratis upgraden kan altijd, opzeggen ook. {{ __('Alle prijzen zijn per jaar en exclusief btw. Start gratis — upgraden kan altijd, opzeggen ook.') }}
</p> </p>
</div> </div>

View file

@ -1,7 +1,7 @@
@extends('marketing.layout') @extends('marketing.layout')
@section('title', 'Pricing — Certified') @section('title', 'Pricing — Certified')
@section('description', 'Eerlijke prijzen voor veilig certificaatbeheer. Start gratis met maximaal 5 certificaten — inclusief OCR, slimme reminders en AVG-proof delen.') @section('description', __('Eerlijke prijzen voor je eigen certificaatbeheer. Start gratis met maximaal 5 certificaten — inclusief OCR, slimme reminders en AVG-proof delen.'))
@section('content') @section('content')
@ -14,10 +14,10 @@
<div class="mx-auto mb-14 max-w-2xl text-center"> <div class="mx-auto mb-14 max-w-2xl text-center">
<p class="animate-fade-in-up text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">Pricing</p> <p class="animate-fade-in-up text-sm font-semibold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">Pricing</p>
<h1 class="animate-fade-in-up mt-3 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-5xl dark:text-white"> <h1 class="animate-fade-in-up mt-3 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-5xl dark:text-white">
Compliance hoeft niet duur te zijn {{ __('Overzicht hoeft niet duur te zijn') }}
</h1> </h1>
<p class="animate-fade-in-up mx-auto mt-5 max-w-xl text-lg leading-relaxed text-slate-600 [animation-delay:200ms] dark:text-slate-400"> <p class="animate-fade-in-up mx-auto mt-5 max-w-xl text-lg leading-relaxed text-slate-600 [animation-delay:200ms] dark:text-slate-400">
E&eacute;n verlopen certificaat kost al snel meer dan een jaar Certified. Start gratis en betaal pas als je meer certificaten beheert. {{ __('Eén verlopen certificaat kost je al snel meer dan een jaar Certified. Start gratis en betaal pas als je meer certificaten beheert.') }}
</p> </p>
</div> </div>
@ -28,15 +28,15 @@
</section> </section>
{{-- ============ VALUE REASSURANCE ============ --}} {{-- ============ VALUE REASSURANCE ============ --}}
<section class="border-y border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/40" aria-label="Inbegrepen bij elk pakket"> <section class="border-y border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/40" aria-label="{{ __('Inbegrepen bij elk pakket') }}">
<div class="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8">
<h2 class="text-center text-2xl font-bold tracking-tight text-slate-900 dark:text-white">Inbegrepen bij elk pakket</h2> <h2 class="text-center text-2xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Inbegrepen bij elk pakket') }}</h2>
<div class="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-4"> <div class="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
@foreach ([ @foreach ([
['title' => 'Europese hosting', 'body' => 'Alle data in EU-datacenters, volledig AVG-proof.'], ['title' => __('Europese hosting'), 'body' => __('Al je data in EU-datacenters, volledig AVG-proof.')],
['title' => 'Automatische OCR', 'body' => 'Titel, uitgever en vervaldatum automatisch herkend.'], ['title' => __('Automatische OCR'), 'body' => __('Titel, uitgever en vervaldatum automatisch herkend.')],
['title' => 'Slimme reminders', 'body' => 'E-mail op het moment dat jij instelt, plus urgente meldingen.'], ['title' => __('Slimme reminders'), 'body' => __('E-mail op het moment dat jij instelt, plus urgente meldingen.')],
['title' => 'Veilig delen', 'body' => 'Ondertekende links (48u) en AES-256 versleutelde ZIP\'s.'], ['title' => __('Veilig delen'), 'body' => __("Ondertekende links (48u) en AES-256 versleutelde ZIP's.")],
] as $item) ] as $item)
<div class="rounded-2xl border border-slate-200 bg-white p-6 text-center transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-slate-900/5 dark:border-slate-800 dark:bg-slate-900 dark:hover:shadow-black/30"> <div class="rounded-2xl border border-slate-200 bg-white p-6 text-center transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-slate-900/5 dark:border-slate-800 dark:bg-slate-900 dark:hover:shadow-black/30">
<span class="mx-auto flex size-10 items-center justify-center rounded-xl bg-emerald-100 dark:bg-emerald-500/15"> <span class="mx-auto flex size-10 items-center justify-center rounded-xl bg-emerald-100 dark:bg-emerald-500/15">
@ -52,15 +52,15 @@
{{-- ============ FAQ ============ --}} {{-- ============ FAQ ============ --}}
@include('marketing.partials.faq', [ @include('marketing.partials.faq', [
'faqTitle' => 'Vragen over prijzen', 'faqTitle' => __('Vragen over prijzen'),
'faqSubtitle' => 'Alles over facturering, opzeggen en overstappen.', 'faqSubtitle' => __('Alles over facturering, opzeggen en overstappen.'),
'faqs' => [ 'faqs' => [
['q' => 'Is het Gratis-pakket echt gratis?', 'a' => 'Ja, voor altijd. Je beheert er tot 5 certificaten mee, inclusief OCR-scan, reminders en veilig delen. Geen creditcard, geen verborgen kosten — pas als je meer dan 5 certificaten wilt bewaken, stap je over op een betaald pakket.'], ['q' => __('Is het Gratis-pakket echt gratis?'), 'a' => __('Ja, voor altijd. Je beheert er tot 5 certificaten mee, inclusief OCR-scan, reminders en veilig delen. Geen creditcard, geen verborgen kosten — pas als je meer dan 5 certificaten wilt bewaken, stap je over op een betaald pakket.')],
['q' => 'Hoe werkt de facturering?', 'a' => 'Betaalde pakketten worden één keer per jaar gefactureerd: €12,50 per jaar voor Starter, €45 per jaar voor Onbeperkt, beide exclusief btw. Je ontvangt een factuur die je direct in je administratie kunt verwerken.'], ['q' => __('Hoe werkt de facturering?'), 'a' => __('Betaalde pakketten worden één keer per jaar gefactureerd: €12,50 per jaar voor Starter, €45 per jaar voor Onbeperkt, beide exclusief btw. Je ontvangt een factuur die je direct in je administratie kunt verwerken.')],
['q' => 'Wat gebeurt er als ik over mijn certificatenlimiet ga?', 'a' => 'Niets ergs — je data blijft gewoon staan. We sturen een melding en je kunt eenvoudig upgraden naar het volgende pakket. We verwijderen nooit certificaten omdat je over een limiet heen bent.'], ['q' => __('Wat gebeurt er als ik over mijn certificatenlimiet ga?'), 'a' => __('Niets ergs — je data blijft gewoon staan. We sturen een melding en je kunt eenvoudig upgraden naar het volgende pakket. We verwijderen nooit certificaten omdat je over een limiet heen bent.')],
['q' => 'Kan ik tussentijds upgraden, downgraden of opzeggen?', 'a' => 'Upgraden kan op elk moment; je betaalt alleen het verschil voor de rest van het jaar. Downgraden of opzeggen gaat in bij de volgende verlengingsdatum. Na opzegging kun je al je data exporteren; daarna verwijderen we alles definitief binnen 30 dagen.'], ['q' => __('Kan ik tussentijds upgraden, downgraden of opzeggen?'), 'a' => __('Upgraden kan op elk moment; je betaalt alleen het verschil voor de rest van het jaar. Downgraden of opzeggen gaat in bij de volgende verlengingsdatum. Na opzegging kun je al je data exporteren; daarna verwijderen we alles definitief binnen 30 dagen.')],
['q' => 'Zit de verwerkersovereenkomst bij de prijs in?', 'a' => 'Ja, bij de betaalde pakketten leveren we een standaard verwerkersovereenkomst zonder extra kosten.'], ['q' => __('Heb ik een verwerkersovereenkomst nodig bij een betaald pakket?'), 'a' => __('Meestal niet — gebruik je Certified voor jezelf, dan ben je geen verwerkingsverantwoordelijke. Wil je er als zzp\'er of eenmanszaak toch één voor je administratie, dan staat de standaard verwerkersovereenkomst gratis voor je klaar op de DPA-pagina.')],
['q' => 'Zijn er kosten per OCR-scan?', 'a' => 'Nee. OCR-scans zitten bij elk pakket inbegrepen — ook bij Gratis. Je betaalt nooit per scan.'], ['q' => __('Zijn er kosten per OCR-scan?'), 'a' => __('Nee. OCR-scans zitten bij elk pakket inbegrepen — ook bij Gratis. Je betaalt nooit per scan.')],
], ],
]) ])

View file

@ -0,0 +1,161 @@
@extends('marketing.layout')
@section('title', __('Privacyverklaring').' — Certified')
@section('description', __('Hoe Certified omgaat met je persoonsgegevens: welke gegevens we verzamelen, waarom, en welke rechten je hebt.'))
@section('content')
@include('marketing.partials.legal-header', ['title' => __('Privacyverklaring'), 'updated' => __('Versie 1.0 · augustus 2026 · AVG/GDPR')])
<section class="mx-auto max-w-3xl px-4 pb-24 sm:px-6">
<div class="space-y-10">
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">1. {{ __('Wie wij zijn') }}</h2>
<div class="mt-4">
@include('marketing.partials.legal-party-block', ['label' => __('Verwerkingsverantwoordelijke')])
</div>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Bckstage exploiteert Certified, een persoonlijke certificatenmanager waarmee je zelf je certificaten, diploma\'s en documenten zoals een VOG bijhoudt. Deze verklaring legt uit welke persoonsgegevens we verzamelen, waarom, hoe lang we ze bewaren en wat je rechten zijn onder de AVG/GDPR.') }}
</p>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Bckstage is verwerkingsverantwoordelijke voor alle persoonsgegevens die worden verwerkt om je Certified te leveren: je accountgegevens en de certificaatgegevens en documenten die je zelf uploadt.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">2. {{ __('Welke gegevens we verzamelen en waarom') }}</h2>
<div class="mt-4 space-y-4">
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('Account en authenticatie') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __('Naam, e-mailadres, gehasht wachtwoord of passkey, taalvoorkeur en sessiegegevens.') }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: tot accountverwijdering + 30 dagen') }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('Certificaatgegevens') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __("Titel, uitgever, certificaatnummer, uitgifte- en vervaldatum, notities en het geüploade document zelf — soms bijzondere persoonsgegevens, zoals gegevens over het strafrechtelijk verleden bij een VOG.") }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: tot verwijdering door jou, of tot 30 dagen na accountopzegging') }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('Beveiligingslogboeken') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __('IP-adres, browser- en apparaatinformatie en inloggebeurtenissen, gebruikt om misbruik te detecteren en beveiligingsincidenten te onderzoeken.') }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: gerechtvaardigd belang (art. 6 lid 1 sub f AVG) · Bewaartermijn: 90 dagen') }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('OCR-verwerking (optioneel)') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __('Schakel je automatische tekstherkenning in, dan wordt het document dat je op dat moment uploadt gestuurd naar de Europese provider die jij zelf kiest: Klippa (Nederland) of Mistral AI (Frankrijk). Zonder deze instelling gebeurt dit niet.') }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: toestemming (art. 6 lid 1 sub a AVG) · Bewaartermijn: niet permanent opgeslagen bij de provider') }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('Servicecommunicatie') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __('We versturen reminders en accountmeldingen via Resend (Verenigde Staten). Open- en kliktracking staat uit; Resend verwerkt alleen het ontvangeradres en de berichtinhoud.') }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: overeenkomst (art. 6 lid 1 sub b AVG) · Bewaartermijn: 90 dagen in maillogboeken') }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-4 dark:border-slate-800">
<h3 class="text-sm font-bold text-slate-900 dark:text-white">{{ __('Facturering (betaalde abonnementen)') }}</h3>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">{{ __('Naam, factuuradres en e-mailadres voor facturering. Betalingsgegevens worden niet door Bckstage zelf opgeslagen.') }}</p>
<p class="mt-2 text-xs text-slate-500 dark:text-slate-500">{{ __('Rechtsgrond: wettelijke verplichting (art. 6 lid 1 sub c AVG) · Bewaartermijn: 7 jaar (Nederlandse belastingwet)') }}</p>
</div>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">3. {{ __('Cookies') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Certified gebruikt uitsluitend strikt noodzakelijke cookies: een sessiecookie om je ingelogd te houden, een CSRF-tokencookie ter beveiliging, en een voorkeurscookie voor je taal en weergave (licht/donker). We gebruiken geen reclamecookies, trackingpixels of analyses van derden. Er is geen cookiebanner nodig omdat we uitsluitend strikt noodzakelijke cookies plaatsen.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">4. {{ __('Derden en internationale doorgiften') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We verkopen, verhuren of verhandelen je persoonsgegevens nooit aan derden. We delen gegevens uitsluitend met de volgende partijen, voor zover van toepassing op jouw gebruik:') }}
</p>
<div class="mt-4 overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-800">
<table class="w-full text-left text-sm">
<thead class="bg-slate-50 text-xs font-semibold tracking-wide text-slate-500 uppercase dark:bg-slate-900/60 dark:text-slate-400">
<tr>
<th class="px-4 py-3">{{ __('Partij') }}</th>
<th class="px-4 py-3">{{ __('Locatie') }}</th>
<th class="px-4 py-3">{{ __('Doel') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-800">
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Resend</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Verenigde Staten') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Bezorging van reminders en accountmeldingen') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Klippa</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Nederland (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('OCR-herkenning — alleen als jij dit inschakelt') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Mistral AI</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Frankrijk (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('OCR-herkenning — alleen als jij dit inschakelt') }}</td>
</tr>
<tr>
<td class="px-4 py-3 font-medium text-slate-800 dark:text-slate-200">Moneybird</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Nederland (EU)') }}</td>
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">{{ __('Facturering van betaalde abonnementen') }}</td>
</tr>
</tbody>
</table>
</div>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Voor de doorgifte aan Resend (Verenigde Staten) steunen we op Standaardcontractuele Clausules op grond van Hoofdstuk V AVG. Klippa, Mistral AI en Moneybird zijn allen binnen de EU gevestigd.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">5. {{ __('Waar je gegevens worden opgeslagen') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Je certificaten en accountgegevens worden opgeslagen in datacenters binnen de Europese Unie. We gebruiken geen Amerikaanse cloudproviders voor hosting, waardoor je gegevens niet onder de Amerikaanse CLOUD Act vallen.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">6. {{ __('Beveiliging') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We versleutelen documenten in rust en tijdens verzending (TLS), scheiden accounts op databaseniveau en ondersteunen phishing-bestendige aanmelding via passkeys. Gedeelde documenten zijn alleen bereikbaar via cryptografisch ondertekende links die automatisch verlopen. Meer details vind je op onze security-pagina. Mochten we een datalek ontdekken met waarschijnlijk hoog risico voor jouw rechten, dan informeren we je zonder onredelijke vertraging conform artikel 34 AVG.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">7. {{ __('Jouw rechten') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Onder de AVG heb je recht op inzage, rectificatie, verwijdering, beperking van de verwerking, gegevensoverdraagbaarheid en bezwaar. Je kunt je certificaten en gegevens grotendeels zelf inzien en exporteren vanuit je account; voor andere verzoeken mail je ons. We reageren binnen één maand. Je hebt ook het recht een klacht in te dienen bij de Autoriteit Persoonsgegevens.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">8. {{ __('Minderjarigen') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Certified is niet bedoeld voor gebruik door personen jonger dan 16 jaar. Vermoed je dat we onbedoeld gegevens van een minderjarige hebben verzameld, neem dan contact met ons op zodat we deze kunnen verwijderen.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">9. {{ __('Wijzigingen') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We kunnen deze verklaring van tijd tot tijd bijwerken. Bij wezenlijke wijzigingen informeren we je per e-mail, ten minste 14 dagen voordat de wijziging ingaat.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">10. {{ __('Contact') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Vragen over deze verklaring of je gegevens? Mail ons via') }} <a href="mailto:privacy@bckstage.cloud" class="font-medium text-emerald-700 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300">privacy@bckstage.cloud</a>.
</p>
</article>
</div>
</section>
@endsection

View file

@ -1,7 +1,7 @@
@extends('marketing.layout') @extends('marketing.layout')
@section('title', 'Security & privacy — Certified') @section('title', __('Security & privacy').' — Certified')
@section('description', 'Hoe Certified gevoelige certificaten beschermt: 100% Europese hosting, AES-256 versleuteld delen en keuze uit Europese OCR-providers. Volledig AVG/GDPR-compliant.') @section('description', __("Hoe Certified je certificaten beschermt: 100% Europese hosting, AES-256 versleuteld delen en keuze uit Europese OCR-providers. Volledig AVG/GDPR-compliant."))
@section('content') @section('content')
@ -12,13 +12,13 @@
<div class="relative mx-auto max-w-3xl px-4 pt-36 pb-16 text-center sm:px-6 sm:pt-44 sm:pb-20"> <div class="relative mx-auto max-w-3xl px-4 pt-36 pb-16 text-center sm:px-6 sm:pt-44 sm:pb-20">
<p class="animate-fade-in-up inline-flex items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-4 py-1.5 text-sm font-medium text-emerald-700 dark:border-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400"> <p class="animate-fade-in-up inline-flex items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-4 py-1.5 text-sm font-medium text-emerald-700 dark:border-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400">
🇪🇺 Privacy-by-design, gebouwd in Europa 🇪🇺 {{ __('Privacy-by-design, gebouwd in Europa') }}
</p> </p>
<h1 class="animate-fade-in-up mt-8 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-5xl dark:text-white"> <h1 class="animate-fade-in-up mt-8 text-4xl font-bold tracking-tight text-balance text-slate-900 [animation-delay:100ms] sm:text-5xl dark:text-white">
Security &amp; privacy als fundament, <span class="bg-gradient-to-r from-emerald-600 to-teal-500 bg-clip-text text-transparent dark:from-emerald-400 dark:to-teal-300">niet als bijzaak</span> {{ __('Security & privacy als fundament,') }} <span class="bg-gradient-to-r from-emerald-600 to-teal-500 bg-clip-text text-transparent dark:from-emerald-400 dark:to-teal-300">{{ __('niet als bijzaak') }}</span>
</h1> </h1>
<p class="animate-fade-in-up mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-slate-600 [animation-delay:200ms] dark:text-slate-400"> <p class="animate-fade-in-up mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-slate-600 [animation-delay:200ms] dark:text-slate-400">
Certified beheert documenten met bijzondere persoonsgegevens VOG's, diploma's, BSN-nummers. Op deze pagina lees je precies hoe we die gegevens beschermen. {{ __("Certified bewaart documenten met bijzondere persoonsgegevens — je VOG en andere gevoelige diploma's en certificaten. Op deze pagina lees je precies hoe we die gegevens beschermen.") }}
</p> </p>
</div> </div>
</section> </section>
@ -27,28 +27,28 @@
<section class="mx-auto max-w-5xl space-y-8 px-4 pb-20 sm:px-6"> <section class="mx-auto max-w-5xl space-y-8 px-4 pb-20 sm:px-6">
@foreach ([ @foreach ([
[ [
'kicker' => '01 — Hosting & jurisdictie', 'kicker' => '01 — '.__('Hosting & jurisdictie'),
'title' => '100% Europese hosting, onder Europees recht', 'title' => __('100% Europese hosting, onder Europees recht'),
'body' => 'Alle applicatie- en documentdata staat in Europese datacenters. We gebruiken bewust geen Amerikaanse hyperscalers: je data valt daardoor niet onder de Amerikaanse CLOUD Act en blijft volledig binnen de reikwijdte van de AVG/GDPR. Back-ups worden versleuteld opgeslagen, eveneens binnen de EU.', 'body' => __('Alle applicatie- en documentdata staat in Europese datacenters. We gebruiken bewust geen Amerikaanse hyperscalers: je data valt daardoor niet onder de Amerikaanse CLOUD Act en blijft volledig binnen de reikwijdte van de AVG/GDPR. Back-ups worden versleuteld opgeslagen, eveneens binnen de EU.'),
'points' => ['Datacenters in de EU, data verlaat Europa nooit', 'Geen Amerikaanse cloudproviders in de keten', 'Versleutelde back-ups binnen de EU'], 'points' => [__('Datacenters in de EU, data verlaat Europa nooit'), __('Geen Amerikaanse cloudproviders in de keten'), __('Versleutelde back-ups binnen de EU')],
], ],
[ [
'kicker' => '02 — Versleuteling', 'kicker' => '02 — '.__('Versleuteling'),
'title' => 'Versleuteld in rust, onderweg en bij het delen', 'title' => __('Versleuteld in rust, onderweg en bij het delen'),
'body' => 'Documenten worden versleuteld opgeslagen en al het verkeer loopt via TLS. Deel je een certificaat met een externe partij, dan gebeurt dat via een AES-256 versleutelde ZIP met een apart verstuurd wachtwoord, of via een ondertekende link die na 48 uur automatisch onbruikbaar wordt. API-sleutels van OCR-providers slaan we versleuteld op.', 'body' => __('Je documenten worden versleuteld opgeslagen en al het verkeer loopt via TLS. Deel je een certificaat met iemand anders, dan gebeurt dat via een AES-256 versleutelde ZIP met een apart verstuurd wachtwoord, of via een ondertekende link die na 48 uur automatisch onbruikbaar wordt. API-sleutels van OCR-providers slaan we versleuteld op.'),
'points' => ['AES-256 versleutelde ZIP-exports', 'Ondertekende deel-links met een maximale geldigheid van 48 uur', 'TLS voor al het verkeer, versleutelde opslag van API-sleutels'], 'points' => [__('AES-256 versleutelde ZIP-exports'), __('Ondertekende deel-links met een maximale geldigheid van 48 uur'), __('TLS voor al het verkeer, versleutelde opslag van API-sleutels')],
], ],
[ [
'kicker' => '03 — OCR-verwerking', 'kicker' => '03 — '.__('OCR-verwerking'),
'title' => 'Jij kiest welke Europese partij je documenten leest', 'title' => __('Jij kiest welke Europese partij je documenten leest'),
'body' => 'Voor de automatische tekstherkenning kies je zelf een verwerker: Klippa (Nederland) of Mistral AI (Frankrijk) — beide Europese bedrijven onder Europese jurisdictie. Wil je helemaal geen externe verwerking? Zet OCR dan simpelweg uit en vul velden handmatig in. Jouw data, jouw keuze.', 'body' => __('Voor de automatische tekstherkenning kies je zelf een verwerker: Klippa (Nederland) of Mistral AI (Frankrijk) — beide Europese bedrijven onder Europese jurisdictie. Wil je helemaal geen externe verwerking? Zet OCR dan simpelweg uit en vul velden handmatig in. Jouw data, jouw keuze.'),
'points' => ['Klippa: Nederlandse document-AI specialist', 'Mistral AI: Frans AI-bedrijf, EU-gehost', 'OCR volledig uitschakelbaar per account'], 'points' => [__('Klippa: Nederlandse document-AI specialist'), __('Mistral AI: Frans AI-bedrijf, EU-gehost'), __('OCR volledig uitschakelbaar per account')],
], ],
[ [
'kicker' => '04 — Toegang & isolatie', 'kicker' => '04 — '.__('Toegang & isolatie'),
'title' => 'Strikte scheiding tussen organisaties en gebruikers', 'title' => __('Strikte scheiding tussen accounts'),
'body' => 'Elke gebruiker ziet uitsluitend de eigen certificaten — afgedwongen op databaseniveau, niet alleen in de interface. Inloggen kan met wachtwoord of passkeys, en gedeelde documenten zijn alleen bereikbaar via cryptografisch ondertekende URL\'s die niet te raden of te vervalsen zijn.', 'body' => __("Jij ziet uitsluitend je eigen certificaten — afgedwongen op databaseniveau, niet alleen in de interface. Inloggen kan met wachtwoord of passkeys, en documenten die je deelt zijn alleen bereikbaar via cryptografisch ondertekende URL's die niet te raden of te vervalsen zijn."),
'points' => ['Data-isolatie afgedwongen op databaseniveau', 'Ondersteuning voor passkeys (phishing-bestendig inloggen)', 'Cryptografisch ondertekende deel-URL\'s'], 'points' => [__('Data-isolatie afgedwongen op databaseniveau'), __('Ondersteuning voor passkeys (phishing-bestendig inloggen)'), __("Cryptografisch ondertekende deel-URL's")],
], ],
] as $block) ] as $block)
<article class="group rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition-all duration-300 hover:border-emerald-300/70 hover:shadow-xl hover:shadow-emerald-500/5 sm:p-10 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-emerald-500/30"> <article class="group rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition-all duration-300 hover:border-emerald-300/70 hover:shadow-xl hover:shadow-emerald-500/5 sm:p-10 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-emerald-500/30">
@ -74,24 +74,31 @@
<svg class="size-7 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" /></svg> <svg class="size-7 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" /></svg>
</span> </span>
<h2 class="max-w-2xl text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white"> <h2 class="max-w-2xl text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
Verwerkersovereenkomst inbegrepen {{ __('Verwerkersovereenkomst op aanvraag') }}
</h2> </h2>
<p class="max-w-2xl text-base leading-relaxed text-slate-600 dark:text-slate-400"> <p class="max-w-2xl text-base leading-relaxed text-slate-600 dark:text-slate-400">
Als jouw organisatie certificaten van medewerkers beheert, ben jij verwerkingsverantwoordelijke en zijn wij verwerker. Daarom leveren we bij elk betaald abonnement een heldere verwerkersovereenkomst zodat je richting de Autoriteit Persoonsgegevens direct kunt aantonen dat het geregeld is. {{ __('Gebruik je Certified voor je eigen certificaten, dan heb je geen verwerkersovereenkomst nodig — je bent zelf geen verwerkingsverantwoordelijke onder de AVG. Werk je als zzp\'er of eenmanszaak en wil je er voor je eigen administratie toch één, dan staat de standaard verwerkersovereenkomst voor je klaar.') }}
</p> </p>
<a
href="{{ route('marketing.dpa') }}"
class="inline-flex items-center gap-2 text-sm font-semibold text-emerald-700 transition-colors hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300"
>
{{ __('Bekijk de verwerkersovereenkomst') }}
<span aria-hidden="true">&rarr;</span>
</a>
</div> </div>
</section> </section>
{{-- ============ FAQ ============ --}} {{-- ============ FAQ ============ --}}
@include('marketing.partials.faq', [ @include('marketing.partials.faq', [
'faqTitle' => 'Vragen over security', 'faqTitle' => __('Vragen over security'),
'faqSubtitle' => 'De vragen die privacy officers en functionarissen gegevensbescherming ons het vaakst stellen.', 'faqSubtitle' => __('De vragen die we het vaakst krijgen over privacy en gegevensbescherming.'),
'faqs' => [ 'faqs' => [
['q' => 'Valt mijn data onder de Amerikaanse CLOUD Act?', 'a' => 'Nee. Certified draait uitsluitend op Europese infrastructuur van Europese leveranciers. Er is geen Amerikaanse partij in de hostingketen, dus de CLOUD Act is niet van toepassing op jouw data.'], ['q' => __('Valt mijn data onder de Amerikaanse CLOUD Act?'), 'a' => __('Nee. Certified draait uitsluitend op Europese infrastructuur van Europese leveranciers. Er is geen Amerikaanse partij in de hostingketen, dus de CLOUD Act is niet van toepassing op jouw data.')],
['q' => 'Wat gebeurt er met een document tijdens de OCR-scan?', 'a' => 'Het document wordt via een beveiligde verbinding naar de door jou gekozen Europese provider (Klippa of Mistral AI) gestuurd, die er tekst uit herkent en het resultaat teruggeeft. Wil je dit niet, dan zet je OCR uit en vul je de velden handmatig in.'], ['q' => __('Wat gebeurt er met een document tijdens de OCR-scan?'), 'a' => __('Het document wordt via een beveiligde verbinding naar de door jou gekozen Europese provider (Klippa of Mistral AI) gestuurd, die er tekst uit herkent en het resultaat teruggeeft. Wil je dit niet, dan zet je OCR uit en vul je de velden handmatig in.')],
['q' => 'Hoe lang blijft een gedeelde link geldig?', 'a' => 'Maximaal 48 uur. Daarna is de ondertekende URL cryptografisch ongeldig en kan niemand het document meer openen — ook niet met de oude link. Versleutelde ZIP-exports blijven beveiligd met AES-256 en het apart verstuurde wachtwoord.'], ['q' => __('Hoe lang blijft een gedeelde link geldig?'), 'a' => __('Maximaal 48 uur. Daarna is de ondertekende URL cryptografisch ongeldig en kan niemand het document meer openen — ook niet met de oude link. Versleutelde ZIP-exports blijven beveiligd met AES-256 en het apart verstuurde wachtwoord.')],
['q' => 'Wie kan er bij de certificaten van mijn organisatie?', 'a' => 'Alleen de gebruikers van jouw eigen account. De scheiding tussen gebruikers wordt op databaseniveau afgedwongen. Onze eigen medewerkers hebben geen toegang tot documentinhoud, behalve wanneer jij daar bij een supportverzoek expliciet toestemming voor geeft.'], ['q' => __('Wie kan er bij mijn certificaten?'), 'a' => __('Alleen jij, vanuit je eigen account. De scheiding tussen accounts wordt op databaseniveau afgedwongen. Onze eigen medewerkers hebben geen toegang tot documentinhoud, behalve wanneer jij daar bij een supportverzoek expliciet toestemming voor geeft.')],
['q' => 'Kan ik al mijn data exporteren of laten verwijderen?', 'a' => 'Ja. Je kunt op elk moment al je certificaten en metadata exporteren. Zeg je op, dan verwijderen we je data — inclusief back-ups — binnen 30 dagen definitief, conform de AVG.'], ['q' => __('Kan ik al mijn data exporteren of laten verwijderen?'), 'a' => __('Ja. Je kunt op elk moment al je certificaten en metadata exporteren. Zeg je op, dan verwijderen we je data — inclusief back-ups — binnen 30 dagen definitief, conform de AVG.')],
], ],
]) ])

View file

@ -0,0 +1,154 @@
@extends('marketing.layout')
@section('title', __('Algemene voorwaarden').' — Certified')
@section('description', __('De gebruiksvoorwaarden voor het gebruik van Certified.'))
@section('content')
@include('marketing.partials.legal-header', ['title' => __('Algemene voorwaarden'), 'updated' => __('Versie 1.0 · augustus 2026')])
<section class="mx-auto max-w-3xl px-4 pb-24 sm:px-6">
<div class="space-y-10">
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 1 — Partijen en definities') }}</h2>
<div class="mt-4 grid gap-4 sm:grid-cols-2">
@include('marketing.partials.legal-party-block', ['label' => __('Aanbieder'), 'email' => 'legal@bckstage.cloud'])
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-5 dark:border-slate-800 dark:bg-slate-900/60">
<p class="text-xs font-bold tracking-widest text-emerald-600 uppercase dark:text-emerald-400">{{ __('Gebruiker') }}</p>
<p class="mt-2 text-base font-semibold text-slate-900 dark:text-white">{{ __('Jij') }}</p>
<p class="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('De natuurlijke persoon die een account aanmaakt bij Certified om zijn eigen certificaten, diploma\'s en documenten te beheren.') }}
</p>
</div>
</div>
<div class="mt-5 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>1.1 {{ __('"Overeenkomst" betekent deze Gebruiksvoorwaarden, samen met onze Privacyverklaring en, indien van toepassing, de Verwerkersovereenkomst.') }}</p>
<p>1.2 {{ __('"Dienst" betekent Certified: het platform waarmee je je eigen certificaten, diploma\'s en vergelijkbare documenten beheert, inclusief automatische OCR-herkenning, reminders en veilig delen.') }}</p>
<p>1.3 {{ __('"Gebruiker" of "je"/"jij" betekent de natuurlijke persoon die een account aanmaakt en de Dienst gebruikt.') }}</p>
<p>1.4 {{ __('"Inhoud" betekent alle certificaten, documenten, gegevens en bestanden die je naar de Dienst uploadt.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 2 — Acceptatie en geschiktheid') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>2.1 {{ __('Door een account aan te maken of de Dienst te gebruiken, ga je akkoord met deze Overeenkomst. Ga je hier niet mee akkoord, dan mag je de Dienst niet gebruiken.') }}</p>
<p>2.2 {{ __('Je verklaart dat je ten minste 16 jaar oud bent.') }}</p>
<p>2.3 {{ __('Je bent zelf verantwoordelijk voor het geven van juiste en actuele gegevens bij registratie, en voor het up-to-date houden daarvan.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 3 — Toegang en accountverplichtingen') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>3.1 {{ __('Zolang je deze Overeenkomst naleeft, verleent de Aanbieder je een beperkt, niet-overdraagbaar recht om de Dienst te gebruiken.') }}</p>
<p>3.2 {{ __('Je account is persoonlijk; het delen van je inloggegevens met anderen is niet toegestaan.') }}</p>
<p>3.3 {{ __('Je bent verantwoordelijk voor het geheimhouden van je wachtwoord of passkey en meldt vermoed onbevoegd gebruik direct aan ons.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 4 — Aanvaardbaar gebruik') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Certified is bedoeld voor het beheren van je eigen certificaten en documenten door jou als individu — niet voor het op grote schaal beheren van certificaten van andere personen namens een bedrijf of organisatie.') }}
</p>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">{{ __('Het volgende is niet toegestaan:') }}</p>
<ul class="mt-3 space-y-2 text-base leading-relaxed text-slate-600 dark:text-slate-400">
@foreach ([
__('Proberen onbevoegde toegang te krijgen tot enig onderdeel van de Dienst of gegevens van andere gebruikers'),
__('Reverse-engineeren, decompileren of afgeleide werken maken van de Dienst'),
__('Malware, virussen of schadelijke code uploaden of verspreiden via de Dienst'),
__('Documenten uploaden die niet van jou zijn, zonder toestemming van de rechthebbende'),
__('Doorverkoop of sublicentiëring van toegang tot de Dienst zonder onze voorafgaande schriftelijke toestemming'),
__('Geautomatiseerd scrapen, crawlen of op grote schaal gegevens onttrekken aan de Dienst'),
__('Elk gebruik dat in strijd is met toepasselijk recht of rechten van derden'),
] as $item)
<li class="flex items-start gap-3">
<svg class="mt-0.5 size-4.5 shrink-0 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
<span>{{ $item }}</span>
</li>
@endforeach
</ul>
<p class="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We behouden ons het recht voor om toegang op te schorten zonder voorafgaande kennisgeving wanneer we een schending van dit artikel constateren of vermoeden.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 5 — Abonnementen en betaling') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>5.1 {{ __('Certified kent een gratis pakket en de betaalde abonnementen Starter en Onbeperkt, zoals beschreven op onze prijzenpagina. Betaalde abonnementen worden jaarlijks vooraf gefactureerd, exclusief btw.') }}</p>
<p>5.2 {{ __('Upgraden kan op elk moment; je betaalt dan alleen het verschil voor de resterende looptijd.') }}</p>
<p>5.3 {{ __('Downgraden of opzeggen van een betaald abonnement gaat in op de eerstvolgende verlengingsdatum. Reeds betaalde bedragen worden niet naar rato terugbetaald.') }}</p>
<p>5.4 {{ __('We kunnen onze prijzen aanpassen met een schriftelijke kennisgeving van ten minste 30 dagen vooraf. Ga je niet akkoord met de nieuwe prijs, dan kun je opzeggen voordat de wijziging ingaat.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 6 — Intellectueel eigendom') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>6.1 {{ __('Wij behouden alle intellectuele eigendomsrechten op de Dienst zelf, waaronder de broncode, het ontwerp en onze merken.') }}</p>
<p>6.2 {{ __('Jij behoudt alle rechten op je eigen Inhoud. Je geeft ons een beperkte licentie om je Inhoud op te slaan, te verwerken en te verzenden, uitsluitend voor zover nodig om de Dienst aan jou te leveren.') }}</p>
<p>6.3 {{ __('We mogen geanonimiseerde, geaggregeerde gegevens die niet tot jou herleidbaar zijn gebruiken om de Dienst te verbeteren.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 7 — Beschikbaarheid en wijzigingen van de Dienst') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We streven naar een betrouwbaar beschikbare Dienst, maar garanderen geen ononderbroken of foutloze toegang. We kunnen de Dienst van tijd tot tijd aanpassen; ingrijpende wijzigingen kondigen we vooraf aan.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 8 — Gegevens, privacy en OCR-verwerking') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>8.1 {{ __('Hoe we met je persoonsgegevens omgaan staat beschreven in onze Privacyverklaring, die integraal onderdeel uitmaakt van deze Overeenkomst.') }}</p>
<p>8.2 {{ __('Automatische tekstherkenning (OCR) vindt alleen plaats als jij dit zelf inschakelt, via een door jou gekozen Europese provider. Je kunt OCR ook uitschakelen en velden handmatig invullen.') }}</p>
<p>8.3 {{ __('De Dienst gebruikt uitsluitend strikt noodzakelijke functionele cookies — geen reclame- of trackingcookies.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 9 — Aansprakelijkheid') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>9.1 {{ __('Certified is een hulpmiddel om je certificaten te overzien en je te herinneren aan vervaldatums — geen garantie dat je nooit een deadline mist. De Dienst wordt geleverd "zoals het is".') }}</p>
<p>9.2 {{ __('Voor zover wettelijk toegestaan zijn wij niet aansprakelijk voor indirecte schade of gevolgschade. Onze totale aansprakelijkheid is beperkt tot het bedrag dat je in de twaalf maanden voorafgaand aan de schadeveroorzakende gebeurtenis aan ons hebt betaald.') }}</p>
<p>9.3 {{ __('Niets in deze Overeenkomst beperkt aansprakelijkheid voor overlijden of persoonlijk letsel door onze nalatigheid, fraude, of andere aansprakelijkheid die niet kan worden uitgesloten onder Nederlands recht.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 10 — Opzeggen en verwijdering van je data') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Je kunt je account op elk moment opzeggen vanuit je instellingen. Je kunt daarvoor al je certificaten en gegevens exporteren. Na opzegging verwijderen we je gegevens, inclusief back-ups, definitief binnen 30 dagen. Wij kunnen je account opschorten of beëindigen bij een wezenlijke schending van artikel 4.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 11 — Wijzigingen van deze voorwaarden') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('We kunnen deze voorwaarden wijzigen door een bijgewerkte versie op deze pagina te plaatsen. Bij ingrijpende wijzigingen informeren we je per e-mail, ten minste 30 dagen voordat de wijziging ingaat. Voortgezet gebruik na die datum geldt als acceptatie.') }}
</p>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Artikel 12 — Toepasselijk recht en bevoegde rechter') }}</h2>
<div class="mt-3 space-y-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
<p>12.1 {{ __('Op deze Overeenkomst is Nederlands recht van toepassing.') }}</p>
<p>12.2 {{ __('Geschillen worden voorgelegd aan de bevoegde rechtbank te Arnhem, Nederland, tenzij dwingend consumentenrecht in jouw woonland anders bepaalt.') }}</p>
</div>
</article>
<article>
<h2 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">{{ __('Vragen over deze voorwaarden?') }}</h2>
<p class="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
{{ __('Neem contact met ons op via') }} <a href="mailto:legal@bckstage.cloud" class="font-medium text-emerald-700 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300">legal@bckstage.cloud</a>.
</p>
</article>
</div>
</section>
@endsection

View file

@ -25,6 +25,8 @@ new #[Title('Add certificate')] class extends Component {
public string $certificate_number = ''; public string $certificate_number = '';
public string $holder_last_name = '';
public string $issued_at = ''; public string $issued_at = '';
public string $expires_at = ''; public string $expires_at = '';
@ -74,6 +76,7 @@ new #[Title('Add certificate')] class extends Component {
'category_id' => ['nullable', 'integer', 'exists:categories,id'], 'category_id' => ['nullable', 'integer', 'exists:categories,id'],
'issuer' => ['nullable', 'string', 'max:255'], 'issuer' => ['nullable', 'string', 'max:255'],
'certificate_number' => ['nullable', 'string', 'max:255'], 'certificate_number' => ['nullable', 'string', 'max:255'],
'holder_last_name' => ['nullable', 'string', 'max:255'],
'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'], 'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'],
'expires_at' => ['required', 'date'], 'expires_at' => ['required', 'date'],
'notes' => ['nullable', 'string', 'max:2000'], 'notes' => ['nullable', 'string', 'max:2000'],
@ -90,6 +93,7 @@ new #[Title('Add certificate')] class extends Component {
'category_id' => $validated['category_id'] ?: null, 'category_id' => $validated['category_id'] ?: null,
'issuer' => $validated['issuer'] ?: null, 'issuer' => $validated['issuer'] ?: null,
'certificate_number' => $validated['certificate_number'] ?: null, 'certificate_number' => $validated['certificate_number'] ?: null,
'holder_last_name' => $validated['holder_last_name'] ?: null,
'issued_at' => $validated['issued_at'] ?: null, 'issued_at' => $validated['issued_at'] ?: null,
'file_path' => $path, 'file_path' => $path,
'expires_at' => $validated['expires_at'], 'expires_at' => $validated['expires_at'],
@ -187,6 +191,7 @@ new #[Title('Add certificate')] class extends Component {
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<flux:input wire:model="issuer" :label="__('Issuing authority')" :placeholder="__('e.g. VCA, Red Cross')" /> <flux:input wire:model="issuer" :label="__('Issuing authority')" :placeholder="__('e.g. VCA, Red Cross')" />
<flux:input wire:model="certificate_number" :label="__('Certificate number')" /> <flux:input wire:model="certificate_number" :label="__('Certificate number')" />
<flux:input wire:model="holder_last_name" :label="__('Holder last name')" :description="__('Used by the public verification page.')" />
<flux:input type="date" wire:model="issued_at" :label="__('Issue date')" /> <flux:input type="date" wire:model="issued_at" :label="__('Issue date')" />
<flux:input type="date" wire:model="expires_at" :label="__('Expiry date')" required /> <flux:input type="date" wire:model="expires_at" :label="__('Expiry date')" required />
</div> </div>

View file

@ -26,6 +26,8 @@ new #[Title('Edit certificate')] class extends Component {
public string $certificate_number = ''; public string $certificate_number = '';
public string $holder_last_name = '';
public string $issued_at = ''; public string $issued_at = '';
public string $expires_at = ''; public string $expires_at = '';
@ -43,6 +45,7 @@ new #[Title('Edit certificate')] class extends Component {
$this->category_id = (string) ($certificate->category_id ?? ''); $this->category_id = (string) ($certificate->category_id ?? '');
$this->issuer = (string) ($certificate->issuer ?? ''); $this->issuer = (string) ($certificate->issuer ?? '');
$this->certificate_number = (string) ($certificate->certificate_number ?? ''); $this->certificate_number = (string) ($certificate->certificate_number ?? '');
$this->holder_last_name = (string) ($certificate->holder_last_name ?? '');
$this->issued_at = $certificate->issued_at?->toDateString() ?? ''; $this->issued_at = $certificate->issued_at?->toDateString() ?? '';
$this->expires_at = $certificate->expires_at->toDateString(); $this->expires_at = $certificate->expires_at->toDateString();
$this->notes = (string) ($certificate->notes ?? ''); $this->notes = (string) ($certificate->notes ?? '');
@ -55,6 +58,7 @@ new #[Title('Edit certificate')] class extends Component {
'category_id' => ['nullable', 'integer', 'exists:categories,id'], 'category_id' => ['nullable', 'integer', 'exists:categories,id'],
'issuer' => ['nullable', 'string', 'max:255'], 'issuer' => ['nullable', 'string', 'max:255'],
'certificate_number' => ['nullable', 'string', 'max:255'], 'certificate_number' => ['nullable', 'string', 'max:255'],
'holder_last_name' => ['nullable', 'string', 'max:255'],
'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'], 'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'],
'expires_at' => ['required', 'date'], 'expires_at' => ['required', 'date'],
'notes' => ['nullable', 'string', 'max:2000'], 'notes' => ['nullable', 'string', 'max:2000'],
@ -76,6 +80,7 @@ new #[Title('Edit certificate')] class extends Component {
'category_id' => $validated['category_id'] ?: null, 'category_id' => $validated['category_id'] ?: null,
'issuer' => $validated['issuer'] ?: null, 'issuer' => $validated['issuer'] ?: null,
'certificate_number' => $validated['certificate_number'] ?: null, 'certificate_number' => $validated['certificate_number'] ?: null,
'holder_last_name' => $validated['holder_last_name'] ?: null,
'issued_at' => $validated['issued_at'] ?: null, 'issued_at' => $validated['issued_at'] ?: null,
'expires_at' => $validated['expires_at'], 'expires_at' => $validated['expires_at'],
'notes' => $validated['notes'] ?: null, 'notes' => $validated['notes'] ?: null,
@ -115,6 +120,7 @@ new #[Title('Edit certificate')] class extends Component {
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<flux:input wire:model="issuer" :label="__('Issuing authority')" :placeholder="__('e.g. VCA, Red Cross')" /> <flux:input wire:model="issuer" :label="__('Issuing authority')" :placeholder="__('e.g. VCA, Red Cross')" />
<flux:input wire:model="certificate_number" :label="__('Certificate number')" /> <flux:input wire:model="certificate_number" :label="__('Certificate number')" />
<flux:input wire:model="holder_last_name" :label="__('Holder last name')" :description="__('Used by the public verification page.')" />
<flux:input type="date" wire:model="issued_at" :label="__('Issue date')" /> <flux:input type="date" wire:model="issued_at" :label="__('Issue date')" />
<flux:input type="date" wire:model="expires_at" :label="__('Expiry date')" required /> <flux:input type="date" wire:model="expires_at" :label="__('Expiry date')" required />
</div> </div>

View file

@ -228,6 +228,7 @@ new #[Title('Certificates')] class extends Component {
:direction="$sortDirection" :direction="$sortDirection"
wire:click="sort('title')" wire:click="sort('title')"
>{{ __('Title') }}</flux:table.column> >{{ __('Title') }}</flux:table.column>
<flux:table.column>{{ __('Certificate number') }}</flux:table.column>
<flux:table.column>{{ __('Category') }}</flux:table.column> <flux:table.column>{{ __('Category') }}</flux:table.column>
<flux:table.column <flux:table.column
sortable sortable
@ -242,19 +243,24 @@ new #[Title('Certificates')] class extends Component {
<flux:table.rows> <flux:table.rows>
@foreach ($this->certificates as $certificate) @foreach ($this->certificates as $certificate)
@php($status = $certificate->expiryStatus(auth()->user()->reminder_months_before)) @php($status = $certificate->expiryStatus(auth()->user()->reminder_months_before))
<flux:table.row :key="$certificate->id"> <flux:table.row
<flux:table.cell> :key="$certificate->id"
x-on:click="Livewire.navigate('{{ route('certificates.show', $certificate) }}')"
class="cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/50"
>
<flux:table.cell @click="event.stopPropagation()">
<flux:checkbox wire:model.live="selected" value="{{ $certificate->id }}" /> <flux:checkbox wire:model.live="selected" value="{{ $certificate->id }}" />
</flux:table.cell> </flux:table.cell>
<flux:table.cell class="font-medium text-zinc-800 dark:text-white"> <flux:table.cell class="font-medium text-zinc-800 dark:text-white">
{{ $certificate->title }} {{ $certificate->title }}
</flux:table.cell> </flux:table.cell>
<flux:table.cell>{{ $certificate->certificate_number ?? '—' }}</flux:table.cell>
<flux:table.cell>{{ $certificate->category?->name ?? '—' }}</flux:table.cell> <flux:table.cell>{{ $certificate->category?->name ?? '—' }}</flux:table.cell>
<flux:table.cell>{{ $certificate->expires_at->format('M j, Y') }}</flux:table.cell> <flux:table.cell>{{ $certificate->expires_at->format('M j, Y') }}</flux:table.cell>
<flux:table.cell> <flux:table.cell>
<flux:badge size="sm" :color="$status->color()">{{ $status->label() }}</flux:badge> <flux:badge size="sm" :color="$status->color()">{{ $status->label() }}</flux:badge>
</flux:table.cell> </flux:table.cell>
<flux:table.cell align="end"> <flux:table.cell align="end" @click="event.stopPropagation()">
<flux:dropdown> <flux:dropdown>
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom" /> <flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom" />
<flux:menu> <flux:menu>

View file

@ -0,0 +1,95 @@
<?php
use App\Models\Certificate;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Title;
use Livewire\Component;
new #[Title('Certificate details')] class extends Component {
public Certificate $certificate;
public function mount(Certificate $certificate): void
{
// Route-model binding respects the global scope, so a foreign
// certificate resolves to a 404.
$this->certificate = $certificate;
}
public function getStatusProperty()
{
return $this->certificate->expiryStatus(Auth::user()->reminder_months_before);
}
public function getFileExtensionProperty(): ?string
{
if (blank($this->certificate->file_path)) {
return null;
}
return strtolower(pathinfo($this->certificate->file_path, PATHINFO_EXTENSION));
}
}; ?>
<section class="mx-auto flex w-full max-w-3xl flex-col gap-6">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<flux:heading size="xl">{{ $certificate->title }}</flux:heading>
<flux:text class="mt-1">{{ __('Certificate details') }}</flux:text>
</div>
<div class="flex items-center gap-3">
<flux:badge :color="$this->status->color()">{{ $this->status->label() }}</flux:badge>
<flux:button :href="route('certificates.edit', $certificate)" wire:navigate icon="pencil-square">
{{ __('Edit') }}
</flux:button>
</div>
</div>
<div class="rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm sm:p-8 dark:border-zinc-800 dark:bg-zinc-900">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<flux:input :label="__('Title')" :value="$certificate->title" readonly />
<flux:input :label="__('Category')" :value="$certificate->category?->name ?? '—'" readonly />
<flux:input :label="__('Issuing authority')" :value="$certificate->issuer ?? '—'" readonly />
<flux:input :label="__('Certificate number')" :value="$certificate->certificate_number ?? '—'" readonly />
<flux:input :label="__('Holder last name')" :value="$certificate->holder_last_name ?? '—'" readonly />
<flux:input :label="__('Issue date')" :value="$certificate->issued_at?->format('M j, Y') ?? '—'" readonly />
<flux:input :label="__('Expiry date')" :value="$certificate->expires_at->format('M j, Y')" readonly />
</div>
@if ($certificate->notes)
<div class="mt-4">
<flux:textarea :label="__('Notes')" :value="$certificate->notes" rows="3" readonly />
</div>
@endif
<div class="mt-6 flex items-center gap-3">
<flux:button :href="route('certificates.download', $certificate)" variant="ghost" icon="arrow-down-tray">
{{ __('Download') }}
</flux:button>
<flux:button :href="route('certificates.index')" wire:navigate variant="ghost">
{{ __('Back to certificates') }}
</flux:button>
</div>
</div>
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
@if (blank($certificate->file_path))
<div class="flex flex-col items-center gap-2 p-12 text-center">
<flux:icon.document class="size-8 text-zinc-400" />
<flux:text>{{ __('No document was uploaded for this certificate.') }}</flux:text>
</div>
@elseif (in_array($this->fileExtension, ['jpg', 'jpeg', 'png'], true))
<img
src="{{ route('certificates.preview', $certificate) }}"
alt="{{ $certificate->title }}"
class="max-h-[75vh] w-full object-contain"
/>
@else
<iframe
src="{{ route('certificates.preview', $certificate) }}"
class="h-[75vh] w-full"
title="{{ $certificate->title }}"
></iframe>
@endif
</div>
</section>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
@include('partials.head', ['title' => $title ?? __('Verify a certificate')])
</head>
<body class="min-h-screen bg-zinc-50 antialiased selection:bg-emerald-200 selection:text-emerald-950 dark:bg-zinc-950 dark:selection:bg-emerald-900 dark:selection:text-emerald-100">
<div class="mx-auto flex min-h-screen w-full max-w-2xl flex-col gap-8 px-4 py-10 sm:py-16">
<div class="flex justify-center">
<x-app-logo href="{{ route('home') }}" />
</div>
@yield('content')
<flux:text class="text-center text-xs">
{{ __('Certificate authenticity check powered by :app.', ['app' => config('app.name')]) }}
</flux:text>
</div>
@fluxScripts
@stack('scripts')
</body>
</html>

View file

@ -0,0 +1,65 @@
@extends('verify.layout')
@section('content')
<flux:card class="space-y-6 rounded-2xl">
<div>
<flux:heading size="xl">{{ __('Verify a certificate') }}</flux:heading>
<flux:text class="mt-2">
{{ __('Enter the certificate number and the last name of the certificate holder to check whether it is genuine and currently valid.') }}
</flux:text>
</div>
@if ($result === 'valid')
<flux:callout icon="check-circle" variant="success" :heading="__('Valid')">
{{ __('This certificate number and last name match a currently valid certificate.') }}
</flux:callout>
@elseif ($result === 'invalid')
<flux:callout icon="x-circle" variant="danger" :heading="__('Invalid')">
{{ __('No currently valid certificate matches that certificate number and last name.') }}
</flux:callout>
@endif
<form method="POST" action="{{ route('verify.check') }}" class="space-y-6">
@csrf
<flux:input
name="certificate_number"
:label="__('Certificate number')"
value="{{ old('certificate_number') }}"
required
autofocus
/>
<flux:input
name="holder_last_name"
:label="__('Holder last name')"
value="{{ old('holder_last_name') }}"
required
/>
@error('certificate_number')
<flux:text class="text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text>
@enderror
@error('holder_last_name')
<flux:text class="text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text>
@enderror
@if (filled(config('services.turnstile.site_key')))
<div class="cf-turnstile" data-sitekey="{{ config('services.turnstile.site_key') }}"></div>
@error('cf-turnstile-response')
<flux:text class="text-sm text-red-600 dark:text-red-400">{{ $message }}</flux:text>
@enderror
@push('scripts')
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
@endpush
@endif
<flux:button type="submit" variant="primary" class="w-full" icon="shield-check">
{{ __('Check certificate') }}
</flux:button>
</form>
</flux:card>
@endsection

View file

@ -1,25 +1,37 @@
<?php <?php
use App\Http\Controllers\CertificateDownloadController; use App\Http\Controllers\CertificateDownloadController;
use App\Http\Controllers\CertificatePreviewController;
use App\Http\Controllers\LanguageController; use App\Http\Controllers\LanguageController;
use App\Http\Controllers\MarketingController; use App\Http\Controllers\MarketingController;
use App\Http\Controllers\SharePageController; use App\Http\Controllers\SharePageController;
use App\Http\Controllers\VerifyController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/', [MarketingController::class, 'home'])->name('home'); Route::get('/', [MarketingController::class, 'home'])->name('home');
Route::get('/security', [MarketingController::class, 'security'])->name('marketing.security'); Route::get('/security', [MarketingController::class, 'security'])->name('marketing.security');
Route::get('/pricing', [MarketingController::class, 'pricing'])->name('marketing.pricing'); Route::get('/pricing', [MarketingController::class, 'pricing'])->name('marketing.pricing');
Route::get('/terms', [MarketingController::class, 'terms'])->name('marketing.terms');
Route::get('/privacy', [MarketingController::class, 'privacy'])->name('marketing.privacy');
Route::get('/dpa', [MarketingController::class, 'dpa'])->name('marketing.dpa');
// Available to guests and authenticated users alike. // Available to guests and authenticated users alike.
Route::get('language/{locale}', LanguageController::class)->name('language.switch'); Route::get('language/{locale}', LanguageController::class)->name('language.switch');
// Public certificate authenticity check. No auth — anyone with a
// certificate number and the holder's last name can confirm it's valid.
Route::get('verify', [VerifyController::class, 'show'])->name('verify.show');
Route::post('verify', [VerifyController::class, 'check'])->middleware('throttle:10,1')->name('verify.check');
Route::middleware(['auth', 'verified'])->group(function () { Route::middleware(['auth', 'verified'])->group(function () {
Route::livewire('dashboard', 'pages::dashboard')->name('dashboard'); Route::livewire('dashboard', 'pages::dashboard')->name('dashboard');
Route::livewire('certificates', 'pages::certificates.index')->name('certificates.index'); Route::livewire('certificates', 'pages::certificates.index')->name('certificates.index');
Route::livewire('certificates/create', 'pages::certificates.create')->name('certificates.create'); Route::livewire('certificates/create', 'pages::certificates.create')->name('certificates.create');
Route::livewire('certificates/{certificate}', 'pages::certificates.show')->name('certificates.show');
Route::livewire('certificates/{certificate}/edit', 'pages::certificates.edit')->name('certificates.edit'); Route::livewire('certificates/{certificate}/edit', 'pages::certificates.edit')->name('certificates.edit');
Route::get('certificates/{certificate}/download', CertificateDownloadController::class)->name('certificates.download'); Route::get('certificates/{certificate}/download', CertificateDownloadController::class)->name('certificates.download');
Route::get('certificates/{certificate}/preview', CertificatePreviewController::class)->name('certificates.preview');
Route::livewire('categories', 'pages::categories.index')->name('categories.index'); Route::livewire('categories', 'pages::categories.index')->name('categories.index');

View file

@ -0,0 +1,91 @@
<?php
use App\Models\Certificate;
use App\Models\User;
test('the verify page is publicly accessible', function () {
$this->get(route('verify.show'))
->assertOk()
->assertSee(__('Verify a certificate'));
});
test('a matching certificate number and last name is reported valid', function () {
Certificate::factory()->for(User::factory())->create([
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
'expires_at' => now()->addYear(),
]);
$this->post(route('verify.check'), [
'certificate_number' => 'cert-1234-abcd',
'holder_last_name' => 'jansen',
])->assertRedirect(route('verify.show'));
$this->get(route('verify.show'))
->assertSee(__('Valid'));
});
test('a mismatched last name is reported invalid', function () {
Certificate::factory()->for(User::factory())->create([
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
'expires_at' => now()->addYear(),
]);
$this->post(route('verify.check'), [
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Bakker',
])->assertRedirect(route('verify.show'));
$this->get(route('verify.show'))
->assertSee(__('Invalid'));
});
test('an unknown certificate number is reported invalid', function () {
$this->post(route('verify.check'), [
'certificate_number' => 'DOES-NOT-EXIST',
'holder_last_name' => 'Bakker',
])->assertRedirect(route('verify.show'));
$this->get(route('verify.show'))
->assertSee(__('Invalid'));
});
test('an expired certificate is reported invalid', function () {
Certificate::factory()->for(User::factory())->create([
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
'expires_at' => now()->subDay(),
]);
$this->post(route('verify.check'), [
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
])->assertRedirect(route('verify.show'));
$this->get(route('verify.show'))
->assertSee(__('Invalid'));
});
test('certificates belonging to any account can be verified, not just the authenticated user\'s own', function () {
Certificate::factory()->for(User::factory())->create([
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
'expires_at' => now()->addYear(),
]);
$this->actingAs(User::factory()->create());
$this->post(route('verify.check'), [
'certificate_number' => 'CERT-1234-ABCD',
'holder_last_name' => 'Jansen',
])->assertRedirect(route('verify.show'));
$this->get(route('verify.show'))
->assertSee(__('Valid'));
});
test('certificate number and last name are required', function () {
$this->post(route('verify.check'), [])
->assertSessionHasErrors(['certificate_number', 'holder_last_name']);
});