|
1 | 1 | /** |
2 | | - * 404 Not Found Page - with search, helpful links, and old URL redirects |
| 2 | + * 404 Not Found Page - v4 recovery scene: outlined giant code with one solid |
| 3 | + * digit, serif accent line, square actions, and a standards marquee. |
3 | 4 | */ |
4 | 5 | import { OpenElement } from '@openelement/element'; |
5 | 6 | import { StyleSheet } from '@openelement/element'; |
6 | 7 | import '@openelement/ui/open-button'; |
7 | | -import '@openelement/ui/open-input'; |
8 | | -import '@openelement/site-ui/open-brand-mark.tsx'; |
9 | | -import '@openelement/site-ui/open-page-hero.tsx'; |
10 | | - |
11 | | -const POPULAR_LINKS = [ |
12 | | - { href: '/guide/getting-started', label: 'Getting Started' }, |
13 | | - { href: '/guide/core-concepts', label: 'Core Concepts' }, |
14 | | - { href: '/architecture/dsd', label: 'DSD Rendering' }, |
15 | | - { href: '/apilist', label: 'API Reference' }, |
16 | | - { href: '/architecture/architecture', label: 'Architecture' }, |
17 | | - { href: '/architecture/comparison', label: 'Framework Comparison' }, |
18 | | - { href: '/roadmap', label: 'Roadmap' }, |
19 | | -]; |
| 8 | + |
| 9 | +const marqueeText = |
| 10 | + 'CUSTOM ELEMENTS ✳ SHADOW DOM ✳ DECLARATIVE SHADOW DOM ✳ ES MODULES ✳ SIGNALS ✳ HTML FIRST ✳ 404 ✳ '; |
20 | 11 |
|
21 | 12 | const styles = new StyleSheet(); |
22 | 13 | styles.replaceSync(` |
23 | 14 | :host { |
24 | 15 | display: block; |
25 | 16 | color: var(--text-primary); |
| 17 | + background: var(--bg-base); |
| 18 | + } |
| 19 | +
|
| 20 | + * { |
| 21 | + box-sizing: border-box; |
| 22 | + } |
| 23 | +
|
| 24 | + h1, |
| 25 | + p { |
| 26 | + margin: 0; |
26 | 27 | } |
27 | | - .container { |
| 28 | +
|
| 29 | + .stage { |
| 30 | + position: relative; |
| 31 | + isolation: isolate; |
28 | 32 | display: grid; |
29 | 33 | justify-items: center; |
30 | | - max-width: none; |
31 | | - min-height: min(700px, calc(100svh - var(--nav-height))); |
32 | | - margin: 0 auto; |
33 | | - padding: var(--size-16) var(--size-6); |
| 34 | + align-content: center; |
| 35 | + gap: var(--size-5); |
| 36 | + min-height: calc(100svh - var(--nav-height) - var(--size-12)); |
| 37 | + padding: clamp(3rem, 8vh, 6rem) var(--size-6); |
34 | 38 | text-align: center; |
35 | 39 | background: |
36 | | - radial-gradient(circle at 50% 22%, color-mix(in srgb, var(--brand-pale) 46%, transparent), transparent 34%), |
| 40 | + radial-gradient(circle at 50% 42%, color-mix(in srgb, var(--violet-5) 18%, transparent), transparent 55%), |
37 | 41 | var(--bg-base); |
38 | 42 | } |
39 | | - .title { |
40 | | - font-size: calc(var(--font-size-8) * 1.35); |
41 | | - font-weight: var(--font-weight-9); |
42 | | - color: var(--text-primary); |
43 | | - letter-spacing: 0; |
44 | | - margin: 0; |
45 | | - line-height: 1; |
| 43 | +
|
| 44 | + .stage::before { |
| 45 | + content: ""; |
| 46 | + position: absolute; |
| 47 | + inset: 0; |
| 48 | + z-index: -1; |
| 49 | + background-image: |
| 50 | + linear-gradient(color-mix(in srgb, var(--violet-6) 7%, transparent) 1px, transparent 1px), |
| 51 | + linear-gradient(90deg, color-mix(in srgb, var(--violet-6) 7%, transparent) 1px, transparent 1px); |
| 52 | + background-size: 72px 72px; |
| 53 | + mask-image: radial-gradient(circle at 50% 45%, black, transparent 75%); |
46 | 54 | } |
47 | | - .mark { width:clamp(5rem,12vw,9rem); margin-bottom:var(--size-6); filter:drop-shadow(0 0 44px color-mix(in srgb,var(--brand) 42%,transparent)); } |
48 | | - .subtitle { |
49 | | - font-size: var(--font-size-4); |
50 | | - font-weight: var(--font-weight-8); |
51 | | - margin: var(--size-3) 0 0; |
| 55 | +
|
| 56 | + .code { |
| 57 | + display: flex; |
| 58 | + font-family: var(--font-mono); |
| 59 | + font-size: clamp(9rem, 26vw, 24rem); |
| 60 | + font-weight: 800; |
| 61 | + line-height: 0.9; |
| 62 | + letter-spacing: -0.06em; |
| 63 | + color: transparent; |
| 64 | + -webkit-text-stroke: 1.5px color-mix(in srgb, var(--violet-5) 55%, transparent); |
| 65 | + user-select: none; |
| 66 | + } |
| 67 | +
|
| 68 | + .code .solid { |
52 | 69 | color: var(--text-primary); |
| 70 | + -webkit-text-stroke: 0; |
53 | 71 | } |
54 | | - .description { |
55 | | - max-width: 520px; |
56 | | - font-size: var(--font-size-1); |
57 | | - color: var(--text-secondary); |
58 | | - margin: var(--size-2) 0 var(--size-7); |
59 | | - line-height: var(--font-lineheight-3); |
| 72 | +
|
| 73 | + .serif-line { |
| 74 | + font-family: var(--font-serif); |
| 75 | + font-style: italic; |
| 76 | + font-weight: 400; |
| 77 | + font-size: clamp(2rem, 5vw, 4rem); |
| 78 | + letter-spacing: -0.01em; |
| 79 | + color: var(--violet-8); |
60 | 80 | } |
61 | | - .search-wrapper { |
62 | | - max-width: 400px; |
63 | | - width: 100%; |
64 | | - margin: 0 auto var(--size-8); |
| 81 | +
|
| 82 | + .lede { |
| 83 | + max-width: 34rem; |
| 84 | + color: var(--text-secondary); |
| 85 | + font-family: var(--font-mono); |
| 86 | + font-size: var(--font-size-0); |
| 87 | + line-height: 1.75; |
65 | 88 | } |
66 | | - .popular-label { |
| 89 | +
|
| 90 | + .requested { |
| 91 | + color: var(--text-muted); |
67 | 92 | font-family: var(--font-mono); |
68 | 93 | font-size: var(--font-size-00); |
69 | | - font-weight: var(--font-weight-8); |
70 | | - text-transform: uppercase; |
71 | | - color: var(--brand); |
72 | | - margin-bottom: var(--size-3); |
73 | | - letter-spacing: 0; |
74 | 94 | } |
75 | | - .links-grid { |
| 95 | +
|
| 96 | + .requested code { |
| 97 | + padding: 0.125rem 0.375rem; |
| 98 | + border: 0.5px solid var(--border); |
| 99 | + border-radius: var(--radius-1); |
| 100 | + background: var(--bg-surface); |
| 101 | + color: var(--text-secondary); |
| 102 | + } |
| 103 | +
|
| 104 | + .actions { |
76 | 105 | display: flex; |
77 | 106 | flex-wrap: wrap; |
78 | | - gap: var(--size-2); |
79 | 107 | justify-content: center; |
80 | | - margin-bottom: var(--size-8); |
| 108 | + gap: var(--size-3); |
| 109 | + margin-block-start: var(--size-3); |
81 | 110 | } |
82 | | - @media (max-width: 600px) { |
83 | | - .container { |
84 | | - padding: 80px 20px 64px; |
85 | | - } |
86 | | - .title { |
87 | | - font-size: var(--font-size-7); |
| 111 | +
|
| 112 | + .marquee { |
| 113 | + overflow: hidden; |
| 114 | + white-space: nowrap; |
| 115 | + border-block: 1px solid var(--border); |
| 116 | + background: var(--surface-1); |
| 117 | + } |
| 118 | +
|
| 119 | + .marquee span { |
| 120 | + display: inline-block; |
| 121 | + padding: var(--size-3) 0; |
| 122 | + color: var(--brand); |
| 123 | + font-family: var(--font-mono); |
| 124 | + font-size: var(--font-size-0); |
| 125 | + font-weight: var(--font-weight-5); |
| 126 | + letter-spacing: 0.12em; |
| 127 | + animation: marquee 36s linear infinite; |
| 128 | + } |
| 129 | +
|
| 130 | + @keyframes marquee { |
| 131 | + to { |
| 132 | + transform: translateX(-50%); |
88 | 133 | } |
89 | | - .subtitle { |
90 | | - font-size: var(--font-size-2); |
| 134 | + } |
| 135 | +
|
| 136 | + @media (prefers-reduced-motion: reduce) { |
| 137 | + .marquee span { |
| 138 | + animation: none; |
91 | 139 | } |
92 | 140 | } |
93 | 141 | `); |
94 | 142 |
|
95 | 143 | export default class Page404 extends OpenElement { |
96 | 144 | static override styles = [styles]; |
97 | 145 | override render() { |
98 | | - const locale = this._getLocale('zh'); |
| 146 | + const locale = this._getLocale('en'); |
99 | 147 | const homeHref = locale === 'en' ? '/' : '/zh/'; |
| 148 | + const docsHref = locale === 'en' ? '/docs' : '/zh/docs'; |
100 | 149 | const requestedPath = typeof globalThis.location === 'undefined' |
101 | 150 | ? '/404' |
102 | 151 | : globalThis.location.pathname; |
103 | 152 | return ( |
104 | | - <main> |
105 | | - <open-page-hero variant='error'> |
106 | | - <span slot='eyebrow'>Recovery scene</span> |
107 | | - <span slot='title'>404</span> |
108 | | - <span slot='lede'>The requested route is not part of the current public map.</span> |
109 | | - </open-page-hero> |
110 | | - <div class='container'> |
111 | | - <open-brand-mark class='mark' size='xl'></open-brand-mark> |
112 | | - <p class='subtitle'>Page not found</p> |
113 | | - <p class='description'> |
114 | | - The page you are looking for doesn't exist or has been moved. |
| 153 | + <main class='notfound'> |
| 154 | + <section class='stage'> |
| 155 | + <h1 class='code' aria-label='404'> |
| 156 | + <span aria-hidden='true'>4</span> |
| 157 | + <span class='solid' aria-hidden='true'>0</span> |
| 158 | + <span aria-hidden='true'>4</span> |
| 159 | + </h1> |
| 160 | + <p class='serif-line'>Lost in the shadow DOM.</p> |
| 161 | + <p class='lede'> |
| 162 | + This route never mounted. The page you want is probably one declarative template away. |
115 | 163 | </p> |
116 | | - <p class='description'> |
| 164 | + <p class='requested'> |
117 | 165 | Requested path: <code>{requestedPath}</code> |
118 | 166 | </p> |
119 | | - <div class='search-wrapper'> |
120 | | - <open-input placeholder='Search docs and API'></open-input> |
121 | | - </div> |
122 | | - <p class='popular-label'>Popular pages</p> |
123 | | - <div class='links-grid'> |
124 | | - {POPULAR_LINKS.map((l) => ( |
125 | | - <open-button size='sm' href={l.href}> |
126 | | - {l.label} |
127 | | - </open-button> |
128 | | - ))} |
| 167 | + <div class='actions'> |
| 168 | + <open-button variant='primary' href={homeHref}> |
| 169 | + Back home |
| 170 | + </open-button> |
| 171 | + <open-button href={docsHref}> |
| 172 | + Read the docs |
| 173 | + </open-button> |
129 | 174 | </div> |
130 | | - <open-button variant='primary' href={homeHref}> |
131 | | - Go home |
132 | | - </open-button> |
| 175 | + </section> |
| 176 | + <div class='marquee' aria-hidden='true'> |
| 177 | + <span>{marqueeText + marqueeText}</span> |
133 | 178 | </div> |
134 | 179 | </main> |
135 | 180 | ); |
|
0 commit comments