diff --git a/app/Http/Controllers/Zunscan/AboutController.php b/app/Http/Controllers/Zunscan/AboutController.php index 280fe09..d3634eb 100644 --- a/app/Http/Controllers/Zunscan/AboutController.php +++ b/app/Http/Controllers/Zunscan/AboutController.php @@ -13,7 +13,7 @@ public function __invoke(): View return view('zunscan.about.index', [ 'title' => __('zunscan.seo.about.title'), 'description' => __('zunscan.seo.about.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), ]); } } diff --git a/app/Http/Controllers/Zunscan/ContactController.php b/app/Http/Controllers/Zunscan/ContactController.php index e70324c..a59f704 100644 --- a/app/Http/Controllers/Zunscan/ContactController.php +++ b/app/Http/Controllers/Zunscan/ContactController.php @@ -13,7 +13,7 @@ public function __invoke(): View return view('zunscan.contact.index', [ 'title' => __('zunscan.seo.contact.title'), 'description' => __('zunscan.seo.contact.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), ]); } } diff --git a/app/Http/Controllers/Zunscan/MediaController.php b/app/Http/Controllers/Zunscan/MediaController.php index 73148ef..1e54677 100644 --- a/app/Http/Controllers/Zunscan/MediaController.php +++ b/app/Http/Controllers/Zunscan/MediaController.php @@ -13,7 +13,7 @@ public function __invoke(): View return view('zunscan.media.index', [ 'title' => __('zunscan.seo.media.title'), 'description' => __('zunscan.seo.media.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), ]); } } diff --git a/app/Http/Controllers/Zunscan/PrivacyController.php b/app/Http/Controllers/Zunscan/PrivacyController.php index 8c3fa42..5281327 100644 --- a/app/Http/Controllers/Zunscan/PrivacyController.php +++ b/app/Http/Controllers/Zunscan/PrivacyController.php @@ -15,7 +15,7 @@ public function __invoke(): View return view('zunscan.legal.privacy', [ 'title' => __('zunscan.seo.privacy.title'), 'description' => __('zunscan.seo.privacy.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), 'body' => Str::markdown(File::get(resource_path('content/zunscan/'.app()->getLocale().'/datenschutz.md'))), ]); } diff --git a/app/Http/Controllers/Zunscan/ServicesScanningController.php b/app/Http/Controllers/Zunscan/ServicesScanningController.php index 123ec33..f6e7c91 100644 --- a/app/Http/Controllers/Zunscan/ServicesScanningController.php +++ b/app/Http/Controllers/Zunscan/ServicesScanningController.php @@ -13,7 +13,7 @@ public function __invoke(): View return view('zunscan.services.show.scanning', [ 'title' => __('zunscan.seo.scanning.title'), 'description' => __('zunscan.seo.scanning.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), ]); } } diff --git a/app/Http/Controllers/Zunscan/StartController.php b/app/Http/Controllers/Zunscan/StartController.php index 030c58f..af0d7b2 100644 --- a/app/Http/Controllers/Zunscan/StartController.php +++ b/app/Http/Controllers/Zunscan/StartController.php @@ -13,7 +13,7 @@ public function __invoke(): View return view('zunscan.start.index', [ 'title' => __('zunscan.seo.start.title'), 'description' => __('zunscan.seo.start.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), ]); } } diff --git a/app/Http/Controllers/Zunscan/TermsController.php b/app/Http/Controllers/Zunscan/TermsController.php index 2a56b38..0c194a9 100644 --- a/app/Http/Controllers/Zunscan/TermsController.php +++ b/app/Http/Controllers/Zunscan/TermsController.php @@ -15,7 +15,7 @@ public function __invoke(): View return view('zunscan.legal.terms', [ 'title' => __('zunscan.seo.imprint.title'), 'description' => __('zunscan.seo.imprint.description'), - 'image' => self::OG_IMAGE, + 'image' => $this->ogImage(), 'body' => Str::markdown(File::get(resource_path('content/zunscan/'.app()->getLocale().'/impressum.md'))), ]); } diff --git a/app/Http/Controllers/Zunscan/ZunscanController.php b/app/Http/Controllers/Zunscan/ZunscanController.php index f950f26..d635111 100644 --- a/app/Http/Controllers/Zunscan/ZunscanController.php +++ b/app/Http/Controllers/Zunscan/ZunscanController.php @@ -4,16 +4,27 @@ namespace App\Http\Controllers\Zunscan; +use App\Enums\LocaleEnum; use App\Http\Controllers\Controller; abstract class ZunscanController extends Controller { /** - * The share image, pinned to the 1200×630 the layout declares in - * og:image:width/height — the previous c_scale,dpr_2.0 transform produced - * whatever size the source happened to be, so the declared dimensions - * would not have matched the file. f_jpg rather than f_auto: social - * crawlers are not browsers and several still do not accept webp. + * The share card, one per language. + * + * Self-hosted rather than the old Cloudinary page photo: that image was the + * bare logo on a blue field, which preview inspectors flag for carrying no + * headline — a share card has to say what the page is about on its own. + * These are 1200×630, matching the dimensions the layout declares. + * + * asset() resolves against the root URL that the Zunscan middleware pins to + * the current host, so this stays absolute and on the right domain even + * though AppServiceProvider forces the main site's root app-wide. */ - protected const string OG_IMAGE = 'https://res.cloudinary.com/codebar/image/upload/c_fill,g_auto,w_1200,h_630,f_jpg,q_auto/www-paperflakes-ch/pages/x7img596iuglmttin6ro.jpg'; + protected function ogImage(): string + { + $suffix = app()->getLocale() === LocaleEnum::EN->value ? 'en' : 'de'; + + return asset("images/seo/og-zunscan-{$suffix}.jpg"); + } } diff --git a/lang/de_CH/zunscan.php b/lang/de_CH/zunscan.php index 8955ebe..eb5b84e 100644 --- a/lang/de_CH/zunscan.php +++ b/lang/de_CH/zunscan.php @@ -19,32 +19,32 @@ 'seo' => [ 'start' => [ - 'title' => 'Herzlich Willkommen', - 'description' => 'zunscan.ch - Dein Scanningcenter: Platzersparnis, Post-Scanning, Volltext-Suche. Starte jetzt in die Zukunft mit maximaler Effizienz!', + 'title' => 'Dokumente scannen und digitalisieren', + 'description' => 'Wir scannen und digitalisieren deine Dokumente in Zunzgen: Platz sparen, revisionssicher archivieren, Volltext durchsuchen.', ], 'about' => [ - 'title' => 'Über uns', - 'description' => 'Starte am Büchel mit uns in die Zukunft der Dokumentenverwaltung. Platzersparnis, Post-Scanning und Volltextsuche. Lokal, regional aus 4455 Zunzgen', + 'title' => 'Über uns: Scanning Center in Zunzgen', + 'description' => 'Ein Scanning Center in Zunzgen, getragen von zwei Firmen aus der Region. Kurze Wege, feste Ansprechpersonen.', ], 'scanning' => [ - 'title' => 'Digitalisierung', - 'description' => 'Scanndienste für Top-Preise: Archivieren von A4 und A3 Seiten, Pläne, Bücher, Bundesordnern & mehr. Zunscan.ch - Dein Partner für digitale Lösungen!', + 'title' => 'Dokumente scannen: Preise pro Ordner', + 'description' => 'Transparente Preise für Bundesordner, Einzelbelege und Sonderformate – GeBüV-konform in der Nordwestschweiz produziert.', ], 'contact' => [ - 'title' => 'Kontakt', - 'description' => 'Zwei Ansprechpersonen, ein Ansprechpartner: zunscan.ch wird von der paperflakes AG betrieben, einem Gemeinschaftsunternehmen von codebar Solutions AG und Real Estate Club GmbH.', + 'title' => 'Kontakt und Standorte', + 'description' => 'Zwei Ansprechpersonen, direkt erreichbar per Mail und Telefon. Standorte in Zunzgen und Oberwil.', ], 'media' => [ - 'title' => 'Medien', - 'description' => 'Entdecke unsere Medienrubrik: Logos, Presseberichte und Neuigkeiten rund um Scannen, Dokumentenverwaltung und OCR. Bleibe informiert bei Zunscan.ch!', + 'title' => 'Medien: Logos und Downloads', + 'description' => 'Logos und Downloads rund um zunscan.ch – für Presse, Partner und Publikationen.', ], 'imprint' => [ 'title' => 'Impressum', - 'description' => 'Keine Gewähr für Inhaltsgenauigkeit, keine Haftung für Links. Urheberrechte: Alle Rechte bei paperflakes AG. Schriftliche Zustimmung für Reproduktion.', + 'description' => 'Impressum der paperflakes AG, Hauptstrasse 91, 4455 Zunzgen. Angaben zu Haftung, Links und Urheberrecht.', ], 'privacy' => [ 'title' => 'Datenschutz', - 'description' => 'Datenschutz gemäss DSGVO: Wir behandeln Ihre Daten vertraulich, nutzen sie nur mit Einwilligung. SSL-Verschlüsselung schützt Ihre Übertragungen. Widerruf möglich.', + 'description' => 'Wie wir mit deinen Daten umgehen: vertraulich, nur mit Einwilligung und SSL-verschlüsselt übertragen.', ], ], @@ -135,30 +135,63 @@ 'subtitle' => 'Wir verwandeln mit hochwertigem Equipment Papier zu Pixel und Ordner zu Bytes', 'prices_eyebrow' => 'Transparent kalkuliert', 'prices_title' => 'Preise', + 'vat_note' => 'Alle Preise in CHF, exkl. 8.1 % MWST.', + + 'trial_title' => 'Kostenloser Probescan', + 'trial_body' => 'Wir digitalisieren einen Ordner gratis. Du siehst das Resultat, bevor du dich entscheidest.', + 'trial_cta' => 'Probescan anfordern', + 'folder_a4_title' => 'Preis pro Bundesordner A4', 'folder_a4_pages' => 'Bis 430 Seiten', - 'folder_a4_price' => 'CHF 64.50', - 'folder_a4_disposal' => 'CHF 1.50 / kg', - 'page_a3a4_title' => 'Preis pro Seite A3/A4', - 'page_a3a4_price' => 'CHF 0.19', - 'page_a3a4_disposal' => 'CHF 1.50 / kg', - 'flat_fee_title' => 'Auftragspauschale', - 'flat_fee_price' => 'Für den ersten Auftrag CHF 499.00', + 'tier_quantity' => 'Menge', + 'tier_price' => 'Preis pro Ordner', + 'tier_1_qty' => '1–19 Ordner', + 'tier_1_price' => 'CHF 64.50', + 'tier_2_qty' => '20–99 Ordner', + 'tier_2_price' => 'CHF 61.25', + 'tier_3_qty' => '100–499 Ordner', + 'tier_3_price' => 'CHF 58.25', + 'tier_4_qty' => 'ab 500 Ordner', + 'tier_4_price' => 'CHF 55.75', + 'tier_note' => 'Die Staffelpreise gelten pro Auftrag. Massgebend ist die Gesamtzahl der übergebenen Ordner.', + 'tier_open' => 'Staffelpreise ansehen', + 'tier_dialog_title' => 'Staffelpreise', + 'close' => 'Schliessen', + + 'page_a3a4_title' => 'Preis pro Seite A3/A4 – lose Belege', + 'page_a3a4_price' => 'CHF 0.22', + + 'setup_title' => 'Setup-Paket', + 'setup_subtitle' => 'einmalig beim ersten Auftrag', + 'setup_price' => 'CHF 499.00', + 'setup_includes' => 'Enthalten', + 'setup_item_pickup' => 'Abholung deiner Unterlagen', + 'setup_item_trial' => 'Kostenloser Probescan', + 'setup_item_structure' => 'Aufbau der Dossier- und Ablagestruktur', + 'setup_note' => 'Ab 100 Ordnern wird das Setup-Paket vollständig an den Auftrag angerechnet.', + 'more_services_title' => 'Weitere Dienstleistungen', + 'disposal_title' => 'Entsorgung', - 'disposal_subtitle' => 'Du erhältst für die Vernichtung ein Zertifikat der Reisswolf Aktenvernichtung AG.', - 'disposal_price_label' => 'Pro Vernichtungsauftrag', - 'disposal_price' => 'CHF 250.00', + 'disposal_price' => 'CHF 2.50 pro Ordner', + 'disposal_subtitle' => 'Du erhältst ein Vernichtungsprotokoll und ein Zertifikat der Reisswolf Aktenvernichtung AG.', + 'return_title' => 'Rückführung', - 'return_subtitle' => 'Du erhältst deine Akten zurück im Übermittlungszustand.', - 'return_folder_label' => 'Ordner A4', - 'return_folder_price' => 'CHF 108.50', + 'return_loose_label' => 'Lose im Karton', + 'return_loose_price' => 'CHF 25.00 pro Ordner', + 'return_loose_body' => 'Deine Unterlagen kommen sortiert, aber ungeheftet im Karton zurück.', + 'return_original_label' => 'Rekonstruiert im Originalzustand', + 'return_original_price' => 'CHF 108.50 pro Ordner', + 'return_original_body' => 'Deine Akten werden im Übermittlungszustand zurückgeheftet.', 'return_page_label' => 'Seite A3/A4', 'return_page_price' => 'CHF 0.31', - 'other_title' => 'Sonstiges', - 'other_subtitle' => 'Du hast ein anderes Format, das du digitalisieren möchtest?', - 'other_price_label' => 'Nach Aufwand', - 'other_price' => 'CHF 90.00 / h', + + 'hourly_title' => 'Sonderformate und Zusatzarbeiten', + 'hourly_production_label' => 'Produktion und Sonderformate', + 'hourly_production_price' => 'CHF 95.00 / h', + 'hourly_dms_label' => 'DMS/ECM-Beratung und Einrichtung', + 'hourly_dms_price' => 'CHF 205.00 / h', + ], ], diff --git a/lang/en_CH/zunscan.php b/lang/en_CH/zunscan.php index 8e04562..0558c89 100644 --- a/lang/en_CH/zunscan.php +++ b/lang/en_CH/zunscan.php @@ -19,32 +19,32 @@ 'seo' => [ 'start' => [ - 'title' => 'Welcome', - 'description' => 'zunscan.ch - your scanning centre: free up office space, digitize incoming mail, search full text in seconds. Start into a more efficient future today!', + 'title' => 'Document scanning and digitization', + 'description' => 'We scan and digitize your documents in Zunzgen: free up space, archive audit-proof, search the full text.', ], 'about' => [ - 'title' => 'About us', - 'description' => 'Start your journey into the future of document management with us. Free up space, digitize incoming mail, and search full text. Local, from 4455 Zunzgen.', + 'title' => 'About us: scanning centre in Zunzgen', + 'description' => 'A scanning centre in Zunzgen, run by two companies from the region. Short distances, named contacts.', ], 'scanning' => [ - 'title' => 'Digitization', - 'description' => 'Document scanning at unbeatable prices: A4 and A3 pages, plans, books, ring binders and more. zunscan.ch - your partner for digital solutions!', + 'title' => 'Document scanning: prices per binder', + 'description' => 'Transparent prices for ring binders, loose documents and special formats – produced in north-western Switzerland.', ], 'contact' => [ - 'title' => 'Contact', - 'description' => 'Two contacts, one partner: zunscan.ch is run by paperflakes AG, a joint venture of codebar Solutions AG and Real Estate Club GmbH.', + 'title' => 'Contact and locations', + 'description' => 'Two named contacts, reachable directly by e-mail and phone. Locations in Zunzgen and Oberwil.', ], 'media' => [ - 'title' => 'Media', - 'description' => 'Logos, press coverage and news about scanning, document management and OCR. Stay up to date with zunscan.ch!', + 'title' => 'Media: logos and downloads', + 'description' => 'Logos and downloads around zunscan.ch – for press, partners and publications.', ], 'imprint' => [ 'title' => 'Imprint', - 'description' => 'No warranty for the accuracy of content, no liability for links. Copyright: all rights reserved by paperflakes AG. Written consent required for reproduction.', + 'description' => 'Imprint of paperflakes AG, Hauptstrasse 91, 4455 Zunzgen. Details on liability, links and copyright.', ], 'privacy' => [ 'title' => 'Privacy', - 'description' => 'Privacy in line with the GDPR: we treat your data confidentially and only process it with your consent. SSL encryption protects your data in transit. Consent can be withdrawn at any time.', + 'description' => 'How we handle your data: confidentially, only with your consent, and transmitted over SSL.', ], ], @@ -135,30 +135,63 @@ 'subtitle' => 'We turn paper into pixels and folders into bytes using top-of-the-line equipment', 'prices_eyebrow' => 'Transparent pricing', 'prices_title' => 'Prices', + 'vat_note' => 'All prices in CHF, excl. 8.1 % VAT.', + + 'trial_title' => 'Free trial scan', + 'trial_body' => 'We digitize one binder free of charge. You see the result before you decide.', + 'trial_cta' => 'Request a trial scan', + 'folder_a4_title' => 'Price per A4 ring binder', 'folder_a4_pages' => 'Up to 430 pages', - 'folder_a4_price' => 'CHF 64.50', - 'folder_a4_disposal' => 'CHF 1.50 / kg', - 'page_a3a4_title' => 'Price per A3/A4 page', - 'page_a3a4_price' => 'CHF 0.19', - 'page_a3a4_disposal' => 'CHF 1.50 / kg', - 'flat_fee_title' => 'Order flat fee', - 'flat_fee_price' => 'CHF 499.00 for your first order', + 'tier_quantity' => 'Quantity', + 'tier_price' => 'Price per binder', + 'tier_1_qty' => '1–19 binders', + 'tier_1_price' => 'CHF 64.50', + 'tier_2_qty' => '20–99 binders', + 'tier_2_price' => 'CHF 61.25', + 'tier_3_qty' => '100–499 binders', + 'tier_3_price' => 'CHF 58.25', + 'tier_4_qty' => '500 binders and up', + 'tier_4_price' => 'CHF 55.75', + 'tier_note' => 'The tiers apply per order. The total number of binders handed over is what counts.', + 'tier_open' => 'View volume pricing', + 'tier_dialog_title' => 'Volume pricing', + 'close' => 'Close', + + 'page_a3a4_title' => 'Price per A3/A4 page – loose documents', + 'page_a3a4_price' => 'CHF 0.22', + + 'setup_title' => 'Setup package', + 'setup_subtitle' => 'one-off, with your first order', + 'setup_price' => 'CHF 499.00', + 'setup_includes' => 'Included', + 'setup_item_pickup' => 'Collection of your documents', + 'setup_item_trial' => 'Free trial scan', + 'setup_item_structure' => 'Setting up the dossier and filing structure', + 'setup_note' => 'From 100 binders the setup package is credited in full against the order.', + 'more_services_title' => 'More services', + 'disposal_title' => 'Disposal', - 'disposal_subtitle' => 'You receive a destruction certificate from Reisswolf Aktenvernichtung AG.', - 'disposal_price_label' => 'Per destruction order', - 'disposal_price' => 'CHF 250.00', + 'disposal_price' => 'CHF 2.50 per binder', + 'disposal_subtitle' => 'You receive a destruction log and a certificate from Reisswolf Aktenvernichtung AG.', + 'return_title' => 'Return shipping', - 'return_subtitle' => 'You get your files back in their original condition.', - 'return_folder_label' => 'A4 folder', - 'return_folder_price' => 'CHF 108.50', + 'return_loose_label' => 'Loose in a box', + 'return_loose_price' => 'CHF 25.00 per binder', + 'return_loose_body' => 'Your documents come back sorted, but unbound, in a box.', + 'return_original_label' => 'Reconstructed as received', + 'return_original_price' => 'CHF 108.50 per binder', + 'return_original_body' => 'Your files are bound back into the condition they arrived in.', 'return_page_label' => 'A3/A4 page', 'return_page_price' => 'CHF 0.31', - 'other_title' => 'Anything else', - 'other_subtitle' => 'Got a different format you would like digitized?', - 'other_price_label' => 'Billed by effort', - 'other_price' => 'CHF 90.00 / h', + + 'hourly_title' => 'Special formats and additional work', + 'hourly_production_label' => 'Production and special formats', + 'hourly_production_price' => 'CHF 95.00 / h', + 'hourly_dms_label' => 'DMS/ECM consulting and setup', + 'hourly_dms_price' => 'CHF 205.00 / h', + ], ], diff --git a/public/favicons/zunscan/apple-touch-icon.png b/public/favicons/zunscan/apple-touch-icon.png new file mode 100644 index 0000000..2edd52d Binary files /dev/null and b/public/favicons/zunscan/apple-touch-icon.png differ diff --git a/public/favicons/zunscan/favicon-96x96.png b/public/favicons/zunscan/favicon-96x96.png new file mode 100644 index 0000000..8bec1b7 Binary files /dev/null and b/public/favicons/zunscan/favicon-96x96.png differ diff --git a/public/favicons/zunscan/site.webmanifest b/public/favicons/zunscan/site.webmanifest new file mode 100644 index 0000000..3c1cdcf --- /dev/null +++ b/public/favicons/zunscan/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "zunscan.ch", + "short_name": "zunscan", + "icons": [ + { + "src": "web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#16395a", + "background_color": "#16395a", + "display": "standalone" +} diff --git a/public/favicons/zunscan/web-app-manifest-192x192.png b/public/favicons/zunscan/web-app-manifest-192x192.png new file mode 100644 index 0000000..3ceb745 Binary files /dev/null and b/public/favicons/zunscan/web-app-manifest-192x192.png differ diff --git a/public/favicons/zunscan/web-app-manifest-512x512.png b/public/favicons/zunscan/web-app-manifest-512x512.png new file mode 100644 index 0000000..355e4be Binary files /dev/null and b/public/favicons/zunscan/web-app-manifest-512x512.png differ diff --git a/public/images/seo/og-zunscan-de.jpg b/public/images/seo/og-zunscan-de.jpg new file mode 100644 index 0000000..8b1d5a5 Binary files /dev/null and b/public/images/seo/og-zunscan-de.jpg differ diff --git a/public/images/seo/og-zunscan-en.jpg b/public/images/seo/og-zunscan-en.jpg new file mode 100644 index 0000000..88b9c41 Binary files /dev/null and b/public/images/seo/og-zunscan-en.jpg differ diff --git a/resources/js/zunscan.js b/resources/js/zunscan.js index d7a509c..1ad0c99 100644 --- a/resources/js/zunscan.js +++ b/resources/js/zunscan.js @@ -1,6 +1,8 @@ import '../css/zunscan.css' import Alpine from '@alpinejs/csp' +import focus from '@alpinejs/focus' window.Alpine = Alpine +Alpine.plugin(focus) Alpine.start() diff --git a/resources/views/components/zunscan/layout.blade.php b/resources/views/components/zunscan/layout.blade.php index ffd8177..7982e55 100644 --- a/resources/views/components/zunscan/layout.blade.php +++ b/resources/views/components/zunscan/layout.blade.php @@ -43,7 +43,7 @@ small thumbnail. The Cloudinary transform pins the same 1200×630. --}} - + @@ -61,13 +61,17 @@ - {{-- Zunscan had no favicon at all, so every page logged a 404 for - /favicon.ico. The logo is already an SVG, so it doubles as the icon. --}} - + {{-- Zunscan's own icon set, mirroring the main site's _favicons partial. + The wordmark SVG is 246×87, so using it directly letterboxed into a + sliver at 16px; these are the paperclip glyph on a square plate. --}} + + + + - + @vite(['resources/js/zunscan.js']) diff --git a/resources/views/components/zunscan/patials/schema.blade.php b/resources/views/components/zunscan/patials/schema.blade.php index 1fed937..081f822 100644 --- a/resources/views/components/zunscan/patials/schema.blade.php +++ b/resources/views/components/zunscan/patials/schema.blade.php @@ -1,42 +1,78 @@ -@props(['title', 'description', 'canonical']) +@props(['title', 'description', 'canonical', 'image' => null]) @php - // A deliberately small graph. The main site's SchemaGraph is not reused: it + // A deliberately small graph compared to the main site's SchemaGraph, which // is built around codebar's own config/company.php and would describe the - // wrong organisation on this domain. + // wrong organisation on this domain. Same shape and same discipline though: + // every node has an @id, and nodes reference each other by @id rather than + // repeating themselves. $root = request()->getSchemeAndHttpHost(); - $company = config('zunscan.company'); + $locale = str_replace('_', '-', app()->getLocale()); + // Absolute, because it is compared against $canonical further down — + // zunscan_route() returns a path by design, and a path never equals an + // absolute URL, which put a breadcrumb on the start page too. + $home = $root.zunscan_route('start.index'); + + $address = [ + '@type' => 'PostalAddress', + 'streetAddress' => $company['street'], + 'postalCode' => $company['postal_code'], + 'addressLocality' => $company['locality'], + 'addressRegion' => 'BL', + 'addressCountry' => 'CH', + ]; + + $organization = [ + '@type' => 'Organization', + '@id' => $root.'/#organization', + 'name' => 'zunscan.ch', + 'legalName' => $company['legal_name'], + 'url' => $root.'/', + 'email' => $company['email'], + 'vatID' => $company['uid'], + 'address' => $address, + 'areaServed' => ['@type' => 'Place', 'name' => 'Nordwestschweiz'], + // The joint venture, stated in the markup as well as in the copy. + 'parentOrganization' => array_map(fn (array $person): array => [ + '@type' => 'Organization', + 'name' => $person['company'], + 'url' => $person['website'], + ], config('zunscan.people')), + 'contactPoint' => array_map(fn (array $person): array => [ + '@type' => 'ContactPoint', + 'contactType' => 'sales', + 'name' => $person['name'], + 'email' => $person['email'], + 'telephone' => str_replace(' ', '', $person['phone']), + 'availableLanguage' => ['de-CH', 'en-CH'], + ], config('zunscan.people')), + ]; + + if ($image) { + $organization['image'] = $image; + } $graph = [ + $organization, [ - '@type' => 'Organization', - '@id' => $root.'/#organization', + // The scanning centre as a findable local business, with the same + // address as the operating company. + '@type' => 'LocalBusiness', + '@id' => $root.'/#localbusiness', 'name' => 'zunscan.ch', - 'legalName' => $company['legal_name'], 'url' => $root.'/', 'email' => $company['email'], - 'vatID' => $company['uid'], - 'address' => [ - '@type' => 'PostalAddress', - 'streetAddress' => $company['street'], - 'postalCode' => $company['postal_code'], - 'addressLocality' => $company['locality'], - 'addressCountry' => 'CH', - ], - // The joint venture, stated in the markup as well as in the copy. - 'parentOrganization' => array_map(fn (array $person): array => [ - '@type' => 'Organization', - 'name' => $person['company'], - 'url' => $person['website'], - ], config('zunscan.people')), + 'address' => $address, + 'parentOrganization' => ['@id' => $root.'/#organization'], + 'priceRange' => 'CHF', ], [ '@type' => 'WebSite', '@id' => $root.'/#website', 'url' => $root.'/', 'name' => 'zunscan.ch', - 'inLanguage' => str_replace('_', '-', app()->getLocale()), + 'inLanguage' => $locale, 'publisher' => ['@id' => $root.'/#organization'], ], [ @@ -45,11 +81,25 @@ 'url' => $canonical, 'name' => $title, 'description' => $description, - 'inLanguage' => str_replace('_', '-', app()->getLocale()), + 'inLanguage' => $locale, 'isPartOf' => ['@id' => $root.'/#website'], + 'about' => ['@id' => $root.'/#organization'], ], ]; + // Breadcrumb only where there is a trail to describe — on the start page it + // would be a single self-referential item, which Google ignores anyway. + if ($canonical !== $home) { + $graph[] = [ + '@type' => 'BreadcrumbList', + '@id' => $canonical.'#breadcrumb', + 'itemListElement' => [ + ['@type' => 'ListItem', 'position' => 1, 'name' => 'zunscan.ch', 'item' => $home], + ['@type' => 'ListItem', 'position' => 2, 'name' => $title], + ], + ]; + } + $payload = json_encode([ '@context' => 'https://schema.org', '@graph' => $graph, diff --git a/resources/views/zunscan/services/show/scanning.blade.php b/resources/views/zunscan/services/show/scanning.blade.php index ba539b3..618dad3 100644 --- a/resources/views/zunscan/services/show/scanning.blade.php +++ b/resources/views/zunscan/services/show/scanning.blade.php @@ -1,27 +1,15 @@ @php - $extras = [ - [ - 'title' => 'zunscan.services.scanning.disposal_title', - 'subtitle' => 'zunscan.services.scanning.disposal_subtitle', - 'rows' => [ - ['zunscan.services.scanning.disposal_price_label', 'zunscan.services.scanning.disposal_price'], - ], - ], - [ - 'title' => 'zunscan.services.scanning.return_title', - 'subtitle' => 'zunscan.services.scanning.return_subtitle', - 'rows' => [ - ['zunscan.services.scanning.return_folder_label', 'zunscan.services.scanning.return_folder_price'], - ['zunscan.services.scanning.return_page_label', 'zunscan.services.scanning.return_page_price'], - ], - ], - [ - 'title' => 'zunscan.services.scanning.other_title', - 'subtitle' => 'zunscan.services.scanning.other_subtitle', - 'rows' => [ - ['zunscan.services.scanning.other_price_label', 'zunscan.services.scanning.other_price'], - ], - ], + $tiers = [ + ['zunscan.services.scanning.tier_1_qty', 'zunscan.services.scanning.tier_1_price'], + ['zunscan.services.scanning.tier_2_qty', 'zunscan.services.scanning.tier_2_price'], + ['zunscan.services.scanning.tier_3_qty', 'zunscan.services.scanning.tier_3_price'], + ['zunscan.services.scanning.tier_4_qty', 'zunscan.services.scanning.tier_4_price'], + ]; + + $setupIncludes = [ + 'zunscan.services.scanning.setup_item_pickup', + 'zunscan.services.scanning.setup_item_trial', + 'zunscan.services.scanning.setup_item_structure', ]; @endphp @@ -30,8 +18,28 @@ :subtitle="__('zunscan.services.scanning.subtitle')"/> - {{ __('zunscan.services.scanning.prices_eyebrow') }} -

{{ __('zunscan.services.scanning.prices_title') }}

+ {{-- The trial scan sits above the tables on purpose: it is the offer that + removes the risk of picking the wrong tariff further down. --}} + +
+

{{ __('zunscan.services.scanning.trial_title') }}

+

{{ __('zunscan.services.scanning.trial_body') }}

+
+ + + {{ __('zunscan.services.scanning.trial_cta') }} + + +
+ +
+ {{ __('zunscan.services.scanning.prices_eyebrow') }} +

{{ __('zunscan.services.scanning.prices_title') }}

+

{{ __('zunscan.services.scanning.vat_note') }}

+
@@ -53,16 +61,65 @@
-
-
-
{{ __('zunscan.common.price') }}
-
{{ __('zunscan.services.scanning.folder_a4_price') }}
-
-
-
{{ __('zunscan.common.disposal') }}
-
{{ __('zunscan.services.scanning.folder_a4_disposal') }}
+ {{-- The card carries only the entry price; the full tier scale + lives in a dialog. Inline, the four rows made this card far + taller than the one beside it and left a hole in the grid. --}} +
+ {{-- Label, tier trigger and price share one row, so this card's + price line sits at the same height as the one beside it. --}} +
+
+ {{ __('zunscan.common.price') }} + + +
+ +
{{ __('zunscan.services.scanning.tier_1_price') }}
+
+ +