Skip to content

Commit 6b2411c

Browse files
committed
fix(ui): unify branded error pages
1 parent b7b0be8 commit 6b2411c

17 files changed

Lines changed: 278 additions & 236 deletions

File tree

apps/desktop/e2e/smoke.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,14 @@ test.describe('desktop shell smoke', () => {
110110
const window = await app.firstWindow()
111111
await window.waitForSelector('#retry', { timeout: 30_000 })
112112
expect(window.url().startsWith('file:')).toBe(true)
113+
await expect(window.locator('.wordmark')).toBeVisible()
114+
await expect(window.locator('.wordmark')).toHaveAttribute('aria-label', 'Sim')
115+
await expect(window.locator('#title')).toHaveText('Can’t connect to Sim')
116+
await expect(window.locator('#status')).toHaveText('Check status')
117+
await expect
118+
.poll(() => window.evaluate(() => document.fonts.check('16px "Season Sans"')))
119+
.toBe(true)
120+
await expect(window.locator('#retry')).toHaveCSS('height', '30px')
121+
await expect(window.locator('#retry')).toHaveCSS('border-radius', '8px')
113122
})
114123
})

apps/desktop/electron-builder.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ files:
1010
- dist/**
1111
- static/**
1212
- package.json
13+
- from: ../sim/public/brand/fonts/SeasonSansUprightsVF.woff2
14+
to: static/SeasonSansUprightsVF.woff2
1315

1416
asar: true
1517

apps/desktop/static/offline.html

Lines changed: 130 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@
44
<meta charset="utf-8" />
55
<meta
66
http-equiv="Content-Security-Policy"
7-
content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'"
7+
content="default-src 'none'; font-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'"
88
/>
9-
<title>Sim Can’t connect</title>
9+
<title>Sim - Can’t connect</title>
1010
<style>
11+
@font-face {
12+
font-family: 'Season Sans';
13+
src:
14+
url('../../sim/public/brand/fonts/SeasonSansUprightsVF.woff2') format('woff2'),
15+
url('./SeasonSansUprightsVF.woff2') format('woff2');
16+
font-style: normal;
17+
font-weight: 300 800;
18+
font-display: block;
19+
}
1120
:root {
12-
color-scheme: light dark;
13-
--bg: #ffffff;
14-
--fg: #0c0c0c;
15-
--muted: #6b6b6b;
16-
--border: #e4e4e4;
17-
--accent: #701ffc;
18-
--accent-fg: #ffffff;
19-
}
20-
@media (prefers-color-scheme: dark) {
21-
:root {
22-
--bg: #0c0c0c;
23-
--fg: #f4f4f4;
24-
--muted: #9a9a9a;
25-
--border: #262626;
26-
}
21+
color-scheme: light;
22+
--bg: #fefefe;
23+
--text-primary: #1a1a1a;
24+
--text-body: #434343;
25+
--text-muted: #7a7a7a;
26+
--text-inverse: #ffffff;
27+
--surface-hover: #f2f2f2;
2728
}
2829
* {
2930
box-sizing: border-box;
@@ -35,111 +36,159 @@
3536
}
3637
body {
3738
display: flex;
38-
align-items: center;
39-
justify-content: center;
39+
flex-direction: column;
40+
min-width: 320px;
41+
padding-top: max(env(titlebar-area-height, 38px), 38px);
4042
background: var(--bg);
41-
color: var(--fg);
42-
font-family:
43-
-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
43+
color: var(--text-primary);
44+
font-family: 'Season Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4445
-webkit-font-smoothing: antialiased;
46+
letter-spacing: 0.02em;
4547
user-select: none;
4648
}
47-
main {
48-
max-width: 420px;
49-
padding: 32px;
50-
text-align: center;
49+
header {
50+
flex: none;
51+
width: 100%;
52+
max-width: 1460px;
53+
margin: 0 auto;
54+
padding: 16px 80px;
55+
-webkit-app-region: drag;
56+
}
57+
.wordmark {
58+
display: block;
59+
width: 37px;
60+
height: 18px;
61+
margin: 6px 0;
62+
color: var(--text-body);
5163
}
52-
.mark {
53-
width: 56px;
54-
height: 56px;
55-
margin: 0 auto 20px;
56-
border-radius: 14px;
57-
background: var(--accent);
64+
main {
5865
display: flex;
66+
flex: 1;
67+
width: 100%;
5968
align-items: center;
6069
justify-content: center;
61-
color: var(--accent-fg);
62-
font-size: 26px;
63-
font-weight: 600;
64-
letter-spacing: -0.02em;
70+
padding: 0 16px 64px;
71+
}
72+
.content {
73+
display: flex;
74+
width: 100%;
75+
max-width: 410px;
76+
flex-direction: column;
77+
align-items: center;
78+
gap: 12px;
79+
text-align: center;
6580
}
6681
h1 {
67-
font-size: 17px;
68-
font-weight: 600;
69-
margin: 0 0 8px;
82+
max-width: 100%;
83+
margin: 0;
84+
font-size: 40px;
85+
font-weight: 400;
86+
letter-spacing: -0.02em;
87+
line-height: 1.1;
88+
text-wrap: balance;
7089
}
7190
p {
72-
font-size: 13px;
73-
line-height: 1.5;
74-
color: var(--muted);
75-
margin: 0 0 24px;
91+
max-width: 100%;
92+
margin: 0;
93+
color: var(--text-muted);
94+
font-size: 18px;
95+
line-height: 28px;
7696
}
7797
.actions {
7898
display: flex;
99+
flex-wrap: wrap;
79100
gap: 8px;
80101
justify-content: center;
102+
margin-top: 12px;
81103
}
82104
button {
105+
display: inline-flex;
106+
height: 30px;
107+
align-items: center;
108+
justify-content: center;
83109
appearance: none;
84-
border: 1px solid var(--border);
110+
border: 0;
85111
background: transparent;
86-
color: var(--fg);
112+
color: var(--text-body);
87113
border-radius: 8px;
88-
padding: 7px 14px;
89-
font-size: 13px;
90-
font-weight: 500;
114+
padding: 0 8px;
115+
font: inherit;
116+
font-size: 14px;
117+
line-height: 20px;
91118
cursor: pointer;
119+
outline: none;
120+
transition:
121+
color 150ms cubic-bezier(0.4, 0, 0.2, 1),
122+
background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
123+
-webkit-app-region: no-drag;
92124
}
93125
button.primary {
94-
background: var(--accent);
95-
border-color: var(--accent);
96-
color: var(--accent-fg);
97-
}
98-
button:active {
99-
opacity: 0.8;
100-
}
101-
.links {
102-
margin-top: 20px;
103-
font-size: 12px;
104-
}
105-
.links a {
106-
color: var(--muted);
107-
text-decoration: none;
108-
margin: 0 8px;
109-
cursor: pointer;
126+
background: var(--text-primary);
127+
color: var(--text-inverse);
110128
}
111-
.links a:hover {
112-
color: var(--fg);
129+
@media (hover: hover) and (pointer: fine) {
130+
button.primary:hover {
131+
background: var(--text-body);
132+
}
133+
button.secondary:hover {
134+
background: var(--surface-hover);
135+
}
113136
}
114137
#detail {
115-
font-size: 11px;
116-
color: var(--muted);
117-
margin-top: 16px;
138+
min-height: 17px;
139+
margin-top: 4px;
140+
color: var(--text-muted);
118141
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
142+
font-size: 11px;
143+
line-height: 1.5;
144+
}
145+
@media (max-width: 720px) {
146+
header {
147+
padding-right: 32px;
148+
padding-left: 32px;
149+
}
150+
}
151+
@media (max-width: 480px) {
152+
header {
153+
padding-right: 20px;
154+
padding-left: 20px;
155+
}
156+
h1 {
157+
font-size: 34px;
158+
}
119159
}
120160
</style>
121161
</head>
122162
<body>
163+
<header aria-label="Sim">
164+
<svg class="wordmark" viewBox="0 0 441 212" role="img" aria-label="Sim">
165+
<g fill="currentColor">
166+
<path d="M0 160.9H29.51C29.51 169.08 32.46 175.61 38.37 180.48C44.27 185.12 52.25 187.44 62.31 187.44C73.24 187.44 81.65 185.34 87.56 181.14C93.46 176.71 96.41 170.85 96.41 163.55C96.41 158.24 94.77 153.82 91.49 150.28C88.43 146.74 82.75 143.86 74.44 141.65L46.24 135.01C32.03 131.47 21.42 126.05 14.43 118.75C7.65 111.45 4.26 101.83 4.26 89.88C4.26 79.93 6.78 71.3 11.81 64C17.05 56.7 24.16 51.06 33.12 47.08C42.3 43.09 52.8 41.1 64.6 41.1C76.41 41.1 86.57 43.2 95.1 47.41C103.84 51.61 110.62 57.47 115.43 64.99C120.46 72.52 123.08 81.48 123.3 91.87H93.79C93.57 83.47 90.84 76.94 85.59 72.3C80.34 67.65 73.02 65.33 63.62 65.33C54 65.33 46.57 67.43 41.32 71.63C36.07 75.83 33.45 81.59 33.45 88.89C33.45 99.73 41.32 107.14 57.06 111.12L85.26 118.09C98.81 121.19 108.98 126.28 115.76 133.35C122.53 140.21 125.92 149.61 125.92 161.56C125.92 171.74 123.19 180.7 117.73 188.44C112.26 195.96 104.72 201.82 95.1 206.03C85.7 210.01 74.55 212 61.65 212C42.85 212 27.87 207.35 16.72 198.06C5.57 188.77 0 176.38 0 160.9Z" />
167+
<path d="M232.8 212H202.13L202.13 49.76H229.54V77.39C232.8 68.34 239.11 60.66 247.81 54.7C256.73 48.52 267.5 45.43 280.12 45.43C294.26 45.43 306.01 49.29 315.36 57.02C324.72 64.75 330.81 75.01 333.64 87.82H328.09C330.27 75.01 336.25 64.75 346.04 57.02C355.83 49.29 367.9 45.43 382.26 45.43C400.54 45.43 414.89 50.84 425.34 61.66C435.78 72.47 441 87.26 441 106.03V212H410.98V113.65C410.98 100.84 407.71 91.02 401.19 84.17C394.88 77.11 386.29 73.58 375.41 73.58C367.79 73.58 361.05 75.34 355.17 78.88C349.52 82.19 345.06 87.04 341.8 93.45C338.53 99.85 336.9 107.36 336.9 115.97V212H306.55V113.32C306.55 100.51 303.4 90.8 297.09 84.17C290.78 77.33 282.19 73.91 271.31 73.91C263.69 73.91 256.95 75.67 251.08 79.21C245.42 82.52 240.96 87.38 237.7 93.78C234.43 99.96 232.8 107.36 232.8 115.97V212Z" />
168+
<path d="M184.83 20.55C184.83 31.9 175.64 41.1 164.29 41.1C152.95 41.1 143.76 31.9 143.76 20.55C143.76 9.2 152.95 0 164.29 0C175.64 0 184.83 9.2 184.83 20.55Z" />
169+
<path d="M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z" />
170+
</g>
171+
</svg>
172+
</header>
123173
<main>
124-
<div class="mark">S</div>
125-
<h1 id="title">Can’t connect to Sim</h1>
126-
<p id="message">
127-
Sim couldn’t reach the server. Check your internet connection, then try again.
128-
</p>
129-
<div class="actions">
130-
<button class="primary" id="retry">Retry</button>
131-
</div>
132-
<div class="links">
133-
<a id="status">Check status</a>
174+
<div class="content">
175+
<h1 id="title">Can’t connect to Sim</h1>
176+
<p id="message">
177+
Sim couldn’t reach the server. Check your internet connection, then try again.
178+
</p>
179+
<div class="actions">
180+
<button class="primary" id="retry">Retry</button>
181+
<button class="secondary" id="status">Check status</button>
182+
</div>
183+
<div id="detail" role="status"></div>
134184
</div>
135-
<div id="detail"></div>
136185
</main>
137186
<script>
138187
const params = new URLSearchParams(location.search)
139188
const copy = {
140189
offline: {
141190
title: 'You’re offline',
142-
message: 'Sim needs an internet connection. Reconnect, then try again — we’ll also retry automatically.',
191+
message: 'Sim needs an internet connection. Reconnect, then try again. We’ll also retry automatically.',
143192
},
144193
dns: {
145194
title: 'Can’t find the server',

apps/docs/app/[lang]/not-found.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ export const metadata = {
88
export default function NotFound() {
99
return (
1010
<DocsPage>
11-
<div className='flex min-h-[70vh] flex-col items-center justify-center gap-4 text-center'>
12-
<h1 className='bg-gradient-to-b from-[var(--brand-accent)] to-[var(--brand-accent-hover)] bg-clip-text font-semibold text-8xl text-transparent'>
13-
404
14-
</h1>
15-
<h2 className='font-semibold text-2xl text-[var(--text-primary)]'>Page Not Found</h2>
16-
<p className='text-[var(--text-muted)]'>
17-
The page you're looking for doesn't exist or has been moved.
18-
</p>
19-
<ChipLink href='/' variant='primary'>
20-
Go home
21-
</ChipLink>
11+
<div className='flex min-h-[60vh] flex-col items-center justify-center px-4 py-24 text-center'>
12+
<div className='flex w-full max-w-[410px] flex-col items-center gap-3'>
13+
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
14+
Page not found
15+
</h1>
16+
<p className='text-[var(--text-muted)] text-lg'>
17+
The page you&apos;re looking for doesn&apos;t exist or has been moved.
18+
</p>
19+
<div className='mt-3 flex flex-wrap items-center justify-center gap-2'>
20+
<ChipLink href='/' variant='primary'>
21+
Return home
22+
</ChipLink>
23+
</div>
24+
</div>
2225
</div>
2326
</DocsPage>
2427
)
Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChipLink } from '@sim/emcn'
22
import type { Metadata } from 'next'
3+
import { StatusPage } from '@/components/status-page'
34

45
export const metadata: Metadata = {
56
title: 'Page Not Found',
@@ -8,19 +9,13 @@ export const metadata: Metadata = {
89

910
export default function BlogAuthorNotFound() {
1011
return (
11-
<main
12-
id='main-content'
13-
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
12+
<StatusPage
13+
title='Author not found'
14+
description="The author you're looking for doesn't exist or has been moved."
1415
>
15-
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16-
Author not found
17-
</h1>
18-
<p className='text-[var(--text-muted)] text-lg'>
19-
The author you&apos;re looking for doesn&apos;t exist or has been moved.
20-
</p>
21-
<ChipLink variant='primary' href='/blog' className='mt-3'>
16+
<ChipLink variant='primary' href='/blog'>
2217
Browse blog
2318
</ChipLink>
24-
</main>
19+
</StatusPage>
2520
)
2621
}
Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChipLink } from '@sim/emcn'
22
import type { Metadata } from 'next'
3+
import { StatusPage } from '@/components/status-page'
34

45
export const metadata: Metadata = {
56
title: 'Page Not Found',
@@ -8,19 +9,13 @@ export const metadata: Metadata = {
89

910
export default function BlogNotFound() {
1011
return (
11-
<main
12-
id='main-content'
13-
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
12+
<StatusPage
13+
title='Post not found'
14+
description="The post you're looking for doesn't exist or has been moved."
1415
>
15-
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16-
Post not found
17-
</h1>
18-
<p className='text-[var(--text-muted)] text-lg'>
19-
The post you&apos;re looking for doesn&apos;t exist or has been moved.
20-
</p>
21-
<ChipLink variant='primary' href='/blog' className='mt-3'>
16+
<ChipLink variant='primary' href='/blog'>
2217
Browse blog
2318
</ChipLink>
24-
</main>
19+
</StatusPage>
2520
)
2621
}

0 commit comments

Comments
 (0)