135 lines
6.5 KiB
PHP
135 lines
6.5 KiB
PHP
@props(['code'])
|
|
@php
|
|
/**
|
|
* The text is looked up from the code rather than passed in.
|
|
*
|
|
* Four of these codes have no hint, and the lang files say so with `null`.
|
|
* Laravel's translator returns the KEY when a line resolves to null, so
|
|
* `__('errors.404.hint')` printed "errors.404.hint" on the page — which is
|
|
* what it did, live, on 403/404/405/429. Lang::has() is false for a null
|
|
* line, so it is the check that actually distinguishes the two cases.
|
|
*/
|
|
$line = fn (string $part) => \Illuminate\Support\Facades\Lang::has("errors.{$code}.{$part}")
|
|
? __("errors.{$code}.{$part}")
|
|
: null;
|
|
|
|
$title = $line('title') ?? (string) $code;
|
|
$body = $line('body');
|
|
$hint = $line('hint');
|
|
@endphp
|
|
<!DOCTYPE html>
|
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<title>{{ $title }} — CluPilot Cloud</title>
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
|
|
{{--
|
|
Self-contained, like the maintenance page and for the same reason: this is
|
|
shown when something has already gone wrong, which includes "mid-deploy" and
|
|
"the asset manifest is missing". Pulling styles in through @vite would throw
|
|
a second exception on top of the first and render nothing at all.
|
|
|
|
The fonts are the site's own, served as static files — if they are missing
|
|
the page degrades to a system serif and still says what it needs to say.
|
|
--}}
|
|
@verbatim
|
|
<style>
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");font-weight:600;font-display:swap}
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-700-normal.woff2") format("woff2");font-weight:700;font-display:swap}
|
|
@font-face{font-family:"Plex Mono";src:url("/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
|
|
|
|
:root{
|
|
--bg:#f6f6f8; --card:#fff; --card-2:#fafafb;
|
|
--ink:#17171c; --body:#43434e; --muted:#6e6e7a;
|
|
--line:#e9e9ee; --line-2:#dcdce4;
|
|
--accent:#f97316; --accent-ink:#b8500a; --accent-wash:#fff3e9;
|
|
--plate:#16151b;
|
|
}
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
html{-webkit-text-size-adjust:100%}
|
|
body{
|
|
font-family:"Plex Sans",-apple-system,BlinkMacSystemFont,sans-serif;
|
|
background:var(--bg);color:var(--body);line-height:1.55;
|
|
min-height:100vh;display:grid;place-items:center;padding:clamp(24px,6vw,64px);
|
|
-webkit-font-smoothing:antialiased;
|
|
}
|
|
main{max-width:560px;width:100%}
|
|
.card{border:1px solid var(--line);border-radius:16px;background:var(--card);
|
|
box-shadow:0 16px 50px rgba(40,24,15,.10);overflow:hidden}
|
|
.head{display:flex;align-items:center;justify-content:space-between;gap:14px;
|
|
padding:16px 24px;border-bottom:1px solid var(--line);background:var(--card-2);
|
|
font-size:13px;font-weight:600;color:var(--muted)}
|
|
.head b{color:var(--accent-ink);font-family:"Plex Mono",monospace;font-weight:500}
|
|
.body{padding:30px 24px 28px}
|
|
.brand{display:inline-flex;align-items:center;gap:9px;font-size:17px;font-weight:700;
|
|
letter-spacing:-.02em;color:var(--ink)}
|
|
.word{font-size:20px;font-weight:700;letter-spacing:-.025em;color:var(--ink)}
|
|
.word span{font-weight:600;color:var(--muted)}
|
|
.mark{width:28px;height:28px;flex:none;
|
|
background:linear-gradient(135deg,#fb923c,#f97316 55%,#c2560a)}
|
|
h1{margin-top:18px;font-weight:700;font-size:clamp(1.6rem,4vw,2.1rem);
|
|
letter-spacing:-.032em;line-height:1.1;color:var(--ink)}
|
|
p{margin-top:12px;color:var(--muted);font-size:15px}
|
|
.hint{margin-top:18px;padding-top:16px;border-top:1px solid var(--line);font-size:14px}
|
|
.acts{display:flex;flex-wrap:wrap;gap:10px;margin-top:26px}
|
|
.btn{display:inline-flex;align-items:center;min-height:46px;padding:0 22px;border-radius:12px;
|
|
font-size:15px;font-weight:600;text-decoration:none;border:1.5px solid transparent;
|
|
transition:background .18s,border-color .18s}
|
|
.ink{background:var(--accent-ink);color:#fff}
|
|
.ink:hover{background:#974208}
|
|
.line{border-color:var(--line-2);color:var(--ink)}
|
|
.line:hover{border-color:var(--ink)}
|
|
.foot{margin-top:12px;font-size:14px}
|
|
.foot a{color:var(--accent-ink);font-weight:600;text-decoration:none}
|
|
.foot a:hover{text-decoration:underline}
|
|
.mark-b{margin-top:22px;font-size:12.5px;color:var(--muted);text-align:center}
|
|
.dot{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:pulse 1.8s ease-in-out infinite}
|
|
@keyframes pulse{50%{opacity:.35}}
|
|
@media (prefers-reduced-motion: reduce){.dot{animation:none}}
|
|
</style>
|
|
@endverbatim
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<div class="card">
|
|
<div class="head"><span>{{ __('errors.heading') }}</span><b>{{ $code }}</b></div>
|
|
<div class="body">
|
|
<span class="brand">
|
|
<svg class="mark" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<defs><linearGradient id="err-mark" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
|
|
<stop stop-color="#FB923C"/><stop offset="0.55" stop-color="#F97316"/><stop offset="1" stop-color="#C2560A"/>
|
|
</linearGradient></defs>
|
|
<rect width="64" height="64" rx="15" fill="url(#err-mark)"/>
|
|
<path d="M22 41h20a8 8 0 0 0 .7-15.97A11 11 0 0 0 21.4 27.2 8.5 8.5 0 0 0 22 44z" fill="#ffffff" fill-opacity="0.22"/>
|
|
<path d="M32 17 44 44l-12-6-12 6z" fill="#ffffff"/>
|
|
</svg>
|
|
{{-- One element. `gap` applies between every child of a flex row and a
|
|
bare text node is a child, so "Clu", <i>Pilot</i> and the space
|
|
between them were being spaced apart — the same fault the
|
|
placeholder shipped with. --}}
|
|
<span class="word">CluPilot <span>Cloud</span></span>
|
|
</span>
|
|
<h1>{{ $title }}</h1>
|
|
@if ($body)
|
|
<p>{{ $body }}</p>
|
|
@endif
|
|
@if ($hint)
|
|
<p class="hint">{{ $hint }}</p>
|
|
@endif
|
|
<div class="acts">
|
|
{{-- Back first: on an error page the thing someone actually wants is
|
|
the page they came from, not the front door. --}}
|
|
<a class="btn ink" href="javascript:history.back()">{{ __('errors.back') }}</a>
|
|
<a class="btn line" href="{{ url('/') }}">{{ __('errors.home') }}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="mark-b">CluPilot Cloud</p>
|
|
</main>
|
|
</body>
|
|
</html>
|