easyfaa 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +558 -0
- data/MIT-LICENSE +22 -0
- data/README.md +256 -0
- data/app/views/layouts/easyfaa.html.erb +24 -0
- data/app/views/recourses/_head.html.erb +3 -0
- data/art/autopilot.svg +9 -0
- data/art/cube.svg +9 -0
- data/art/design.svg +8 -0
- data/art/down.html.erb +95 -0
- data/art/easyfaa.svg +7 -0
- data/art/fountain.svg +9 -0
- data/art/goldrush.svg +7 -0
- data/art/houston.svg +8 -0
- data/art/icons.txt +185 -0
- data/art/index.html.erb +157 -0
- data/config/locales/easyfaa.en.yml +7 -0
- data/lib/easyfaa/copy.rb +5 -0
- data/lib/easyfaa/down.rb +19 -0
- data/lib/easyfaa/engine.rb +53 -0
- data/lib/easyfaa/helpers/flows.rb +53 -0
- data/lib/easyfaa/helpers/heads.rb +60 -0
- data/lib/easyfaa/helpers/logos.rb +43 -0
- data/lib/easyfaa/helpers.rb +11 -0
- data/lib/easyfaa/marks.rb +16 -0
- data/lib/easyfaa/version.rb +4 -0
- data/lib/easyfaa.rb +39 -0
- data/public/404.html +118 -0
- data/public/_headers +28 -0
- data/public/bh/css/bh.css +1 -0
- data/public/bh/js/bh.js +152 -0
- data/public/bh/theme/bootstrap.css +10 -0
- data/public/bh/theme/dawn.css +189 -0
- data/public/bh/theme/dracula.css +189 -0
- data/public/bh/theme/gruvbox.css +189 -0
- data/public/bh/theme/monokai.css +190 -0
- data/public/bh/theme/nord.css +189 -0
- data/public/bh/theme/one_dark.css +189 -0
- data/public/bh/theme/solarized.css +188 -0
- data/public/bh/theme/tokyo_night.css +189 -0
- data/public/css/easyfaa.css +1 -0
- data/public/down/autopilot.html +87 -0
- data/public/down/cube.html +87 -0
- data/public/down/design.html +87 -0
- data/public/down/easyfaa.html +84 -0
- data/public/down/fountain.html +87 -0
- data/public/down/goldrush.html +87 -0
- data/public/down/houston.html +87 -0
- data/public/favicon/autopilot-180.png +0 -0
- data/public/favicon/autopilot-192.png +0 -0
- data/public/favicon/autopilot-512.png +0 -0
- data/public/favicon/autopilot-96.png +0 -0
- data/public/favicon/autopilot-dev.svg +9 -0
- data/public/favicon/autopilot.ico +0 -0
- data/public/favicon/autopilot.svg +9 -0
- data/public/favicon/autopilot.webmanifest +12 -0
- data/public/favicon/cube-180.png +0 -0
- data/public/favicon/cube-192.png +0 -0
- data/public/favicon/cube-512.png +0 -0
- data/public/favicon/cube-96.png +0 -0
- data/public/favicon/cube-dev.svg +9 -0
- data/public/favicon/cube.ico +0 -0
- data/public/favicon/cube.svg +9 -0
- data/public/favicon/cube.webmanifest +12 -0
- data/public/favicon/design-180.png +0 -0
- data/public/favicon/design-192.png +0 -0
- data/public/favicon/design-512.png +0 -0
- data/public/favicon/design-96.png +0 -0
- data/public/favicon/design-dev.svg +8 -0
- data/public/favicon/design.ico +0 -0
- data/public/favicon/design.svg +8 -0
- data/public/favicon/design.webmanifest +12 -0
- data/public/favicon/easyfaa-180.png +0 -0
- data/public/favicon/easyfaa-192.png +0 -0
- data/public/favicon/easyfaa-512.png +0 -0
- data/public/favicon/easyfaa-96.png +0 -0
- data/public/favicon/easyfaa-dev.svg +7 -0
- data/public/favicon/easyfaa.ico +0 -0
- data/public/favicon/easyfaa.svg +7 -0
- data/public/favicon/easyfaa.webmanifest +12 -0
- data/public/favicon/fountain-180.png +0 -0
- data/public/favicon/fountain-192.png +0 -0
- data/public/favicon/fountain-512.png +0 -0
- data/public/favicon/fountain-96.png +0 -0
- data/public/favicon/fountain-dev.svg +9 -0
- data/public/favicon/fountain.ico +0 -0
- data/public/favicon/fountain.svg +9 -0
- data/public/favicon/fountain.webmanifest +12 -0
- data/public/favicon/goldrush-180.png +0 -0
- data/public/favicon/goldrush-192.png +0 -0
- data/public/favicon/goldrush-512.png +0 -0
- data/public/favicon/goldrush-96.png +0 -0
- data/public/favicon/goldrush-dev.svg +7 -0
- data/public/favicon/goldrush.ico +0 -0
- data/public/favicon/goldrush.svg +7 -0
- data/public/favicon/goldrush.webmanifest +12 -0
- data/public/favicon/houston-180.png +0 -0
- data/public/favicon/houston-192.png +0 -0
- data/public/favicon/houston-512.png +0 -0
- data/public/favicon/houston-96.png +0 -0
- data/public/favicon/houston-dev.svg +8 -0
- data/public/favicon/houston.ico +0 -0
- data/public/favicon/houston.svg +8 -0
- data/public/favicon/houston.webmanifest +12 -0
- data/public/favicon.ico +0 -0
- data/public/font/bootstrap-icons-6LILWPE2.woff2 +0 -0
- data/public/font/bootstrap-icons-FRBPO3H7.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-latin-LA7QXWWL.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-latin-ext-X4QHZICY.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-latin-ext.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-latin.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-vietnamese-24B6BSND.woff2 +0 -0
- data/public/font/ibm-plex-sans-500-vietnamese.woff2 +0 -0
- data/public/index.html +165 -0
- data/public/js/easyfaa.js +1 -0
- data/public/logo/autopilot.png +0 -0
- data/public/logo/autopilot.svg +9 -0
- data/public/logo/cube.png +0 -0
- data/public/logo/cube.svg +9 -0
- data/public/logo/design.png +0 -0
- data/public/logo/design.svg +8 -0
- data/public/logo/easyfaa.png +0 -0
- data/public/logo/easyfaa.svg +7 -0
- data/public/logo/fountain.png +0 -0
- data/public/logo/fountain.svg +9 -0
- data/public/logo/goldrush.png +0 -0
- data/public/logo/goldrush.svg +7 -0
- data/public/logo/houston.png +0 -0
- data/public/logo/houston.svg +8 -0
- data/public/page/400.html +118 -0
- data/public/page/404.html +118 -0
- data/public/page/406-unsupported-browser.html +118 -0
- data/public/page/422.html +118 -0
- data/public/page/500.html +118 -0
- data/public/site.webmanifest +12 -0
- data/public/theme/bootstrap.css +10 -0
- data/public/theme/dawn.css +189 -0
- data/public/theme/dracula.css +189 -0
- data/public/theme/gruvbox.css +189 -0
- data/public/theme/monokai.css +190 -0
- data/public/theme/nord.css +189 -0
- data/public/theme/one_dark.css +189 -0
- data/public/theme/solarized.css +188 -0
- data/public/theme/tokyo_night.css +189 -0
- data/public/v0.20.0/bh/css/bh.css +1 -0
- data/public/v0.20.0/bh/js/bh.js +152 -0
- data/public/v0.20.0/bh/theme/bootstrap.css +10 -0
- data/public/v0.20.0/bh/theme/dawn.css +189 -0
- data/public/v0.20.0/bh/theme/dracula.css +189 -0
- data/public/v0.20.0/bh/theme/gruvbox.css +189 -0
- data/public/v0.20.0/bh/theme/monokai.css +190 -0
- data/public/v0.20.0/bh/theme/nord.css +189 -0
- data/public/v0.20.0/bh/theme/one_dark.css +189 -0
- data/public/v0.20.0/bh/theme/solarized.css +188 -0
- data/public/v0.20.0/bh/theme/tokyo_night.css +189 -0
- data/public/v0.20.0/css/easyfaa.css +1 -0
- data/public/v0.20.0/font/bootstrap-icons-FRBPO3H7.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-latin-LA7QXWWL.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-latin-ext-X4QHZICY.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-latin-ext.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-latin.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-vietnamese-24B6BSND.woff2 +0 -0
- data/public/v0.20.0/font/ibm-plex-sans-500-vietnamese.woff2 +0 -0
- data/public/v0.20.0/js/easyfaa.js +1 -0
- metadata +264 -0
data/art/index.html.erb
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang='en'>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset='utf-8'>
|
|
5
|
+
<title>easyfaa design</title>
|
|
6
|
+
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
|
7
|
+
<meta name='color-scheme' content='light dark'>
|
|
8
|
+
<meta name='description' content='Every asset a easyfaa app loads, and every component the easyfaa gem draws, in one place.'>
|
|
9
|
+
<link rel='icon' href='favicon/design.svg' type='image/svg+xml'>
|
|
10
|
+
<link rel='icon' href='favicon/design-96.png' type='image/png' sizes='96x96'>
|
|
11
|
+
<link rel='apple-touch-icon' href='favicon/design-180.png'>
|
|
12
|
+
<link rel='manifest' href='site.webmanifest'>
|
|
13
|
+
<!-- The same files every easyfaa page loads, under the version the head helper
|
|
14
|
+
pins a host to, so the page is drawn by the bytes it hands out rather than by
|
|
15
|
+
whatever was last built. Relative, so it reads the same on staging, on production
|
|
16
|
+
and from a folder on disk. -->
|
|
17
|
+
<link rel='stylesheet' href='v<%= version %>/bh/css/bh.css'>
|
|
18
|
+
<link rel='stylesheet' href='v<%= version %>/css/easyfaa.css'>
|
|
19
|
+
<link rel='stylesheet' href='v<%= version %>/bh/theme/bootstrap.css' data-recourse-theme=''>
|
|
20
|
+
<script type='module' src='v<%= version %>/bh/js/bh.js'></script>
|
|
21
|
+
<script type='module' src='v<%= version %>/js/easyfaa.js'></script>
|
|
22
|
+
<style>
|
|
23
|
+
/* The page's own chrome and nothing else: everything shown below is styled by
|
|
24
|
+
the pinned easyfaa.css alone, which is the point of showing it. */
|
|
25
|
+
.design-section { padding-block: 2rem; border-block-end: var(--bs-border-width) solid var(--bs-border-color); }
|
|
26
|
+
.design-section > p { max-width: 48rem; margin-inline: auto; }
|
|
27
|
+
.design-subsection { margin-block-start: 2.5rem; padding-block-start: 1.5rem; border-block-start: var(--bs-border-width) dashed var(--bs-border-color); }
|
|
28
|
+
.design-mark { width: 1.5rem; height: 1.5rem; }
|
|
29
|
+
.design-logo { width: 2.5rem; height: 2.5rem; }
|
|
30
|
+
.design-url { font-family: var(--bs-font-monospace); font-size: var(--bs-font-size-sm); color: var(--bs-fg-2); overflow-wrap: anywhere; }
|
|
31
|
+
.design-shell { height: 18rem; border: var(--bs-border-width) solid var(--bs-border-color); border-radius: var(--bs-radius-3); overflow: hidden; }
|
|
32
|
+
.design-shell .recourse-shell { height: 100%; }
|
|
33
|
+
.chat-thread { max-width: 36rem; margin-inline: auto; }
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body class='text-center'>
|
|
37
|
+
<header class='container-fluid px-3 py-3 border-bottom d-flex flex-wrap justify-content-center align-items-baseline gap-3'>
|
|
38
|
+
<a class='lockup plex' href='#'><svg aria-hidden='true' style='width: 1.0515em; height: 1.0515em; top: 0.18625em' viewBox='0 0 512 512'><g fill='none' stroke='#5aa9e6' stroke-width='28' stroke-linecap='round' stroke-linejoin='round' transform='rotate(45 256 256)'><path d='M196 60 L316 60 L316 372 L196 372 Z'/><path d='M196 120 L316 120'/><path d='M196 372 L256 470 L316 372'/><path d='M232 431 L256 470 L280 431 Z' fill='#0b1e3a'/></g></svg>design</a>
|
|
39
|
+
<nav class='fg-2'>the <a href='#lockup'>lockups</a>, <a href='#theme'>themes</a>, <a href='#page'>pages</a>, <a href='#favicon'>icons</a> shared by <a href='https://pro.houseaccount.com/bookings' title='Fountain'><img class='design-mark' src='logo/fountain.svg' alt='Fountain'></a> <a href='https://gold.houseaccount.com/specialty_matches' title='Goldrush'><img class='design-mark' src='logo/goldrush.svg' alt='Goldrush'></a> <a href='https://providers.houseaccount.com/providers' title='Houston'><img class='design-mark' src='logo/houston.svg' alt='Houston'></a> <a href='https://checkout.houseaccount.com/providers' title='Autopilot'><img class='design-mark' src='logo/autopilot.svg' alt='Autopilot'></a></nav>
|
|
40
|
+
</header>
|
|
41
|
+
|
|
42
|
+
<main class='container-fluid px-3'>
|
|
43
|
+
<section class='design-section'>
|
|
44
|
+
<h1 class='plex'>Multiple apps, same front-end</h1>
|
|
45
|
+
<div class='row g-3 mx-auto' style='max-width: 60rem'>
|
|
46
|
+
<div class='col-12 md:col-6'>
|
|
47
|
+
<div class='card p-3'>
|
|
48
|
+
<strong>Stylesheet</strong>
|
|
49
|
+
<a class='design-url' href='css/easyfaa.css'><span class='d-none sm:d-inline'>https://design.easyfaa.com/</span>css/easyfaa.css</a>
|
|
50
|
+
<span class='small fg-2 mt-1'>bh's Bootstrap 6, then the icon subset and the brand</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class='col-12 md:col-6'>
|
|
54
|
+
<div class='card p-3'>
|
|
55
|
+
<strong>Script</strong>
|
|
56
|
+
<a class='design-url' href='js/easyfaa.js'><span class='d-none sm:d-inline'>https://design.easyfaa.com/</span>js/easyfaa.js</a>
|
|
57
|
+
<span class='small fg-2 mt-1'>bh's bundle, and the controller that paints the wall</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class='col-12'>
|
|
61
|
+
<div class='card p-3'>
|
|
62
|
+
<strong>Pinned to a version — the latest is <%= version %></strong>
|
|
63
|
+
<span class='design-url'><a href='v<%= version %>/css/easyfaa.css'>v<%= version %>/css/easyfaa.css</a> · <a href='v<%= version %>/js/easyfaa.js'>v<%= version %>/js/easyfaa.js</a> · <a href='v<%= version %>/bh/theme/nord.css'>v<%= version %>/bh/theme/nord.css</a></span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<section class='design-section' id='theme'>
|
|
70
|
+
<h2>Themes</h2>
|
|
71
|
+
<button type='button' class='btn btn-outline theme-primary' data-controller='scheme' data-action='scheme#rotate'
|
|
72
|
+
data-scheme-themes-value='["bootstrap","dawn","dracula","gruvbox","monokai","nord","one_dark","solarized","tokyo_night"]'
|
|
73
|
+
data-scheme-path-value='v<%= version %>/bh/theme' data-scheme-storage-value='recourse-scheme'>
|
|
74
|
+
<span class='recourse-scheme-light'><i class='bi bi-moon-fill'></i> Switch to dark</span>
|
|
75
|
+
<span class='recourse-scheme-dark'><i class='bi bi-sun-fill'></i> Switch to light</span>
|
|
76
|
+
</button>
|
|
77
|
+
</section>
|
|
78
|
+
|
|
79
|
+
<section class='design-section' id='lockup'>
|
|
80
|
+
<h2>Lockups</h2>
|
|
81
|
+
<p class='fg-2'>A mark beside its name, as one word and one link. Each drawing fills its 512 grid
|
|
82
|
+
differently, so equal sizes never align: every mark is rendered at a box of its own and pushed
|
|
83
|
+
down by its own nudge, and its ink then stands on the baseline and reaches IBM Plex Sans's ascender.
|
|
84
|
+
The two blues are the mark's rather than the theme's, so a lockup reads the same in every app.</p>
|
|
85
|
+
<div class='d-flex flex-wrap justify-content-center align-items-baseline gap-4 mt-3'>
|
|
86
|
+
<%- mark.each_key do |app| -%>
|
|
87
|
+
<a class='lockup plex' href='logo/<%= app %>.svg'><%= mark[app] %><%- if app == :easyfaa -%><span class='easy'>easy</span>faa<%- else -%><%= app %><%- end -%></a>
|
|
88
|
+
<%- end -%>
|
|
89
|
+
</div>
|
|
90
|
+
<p class='fg-2 mt-4 mb-0'>At three sizes, to show the seating holds:</p>
|
|
91
|
+
<div class='d-flex flex-wrap justify-content-center align-items-baseline gap-4 mt-2'>
|
|
92
|
+
<a class='lockup plex' href='#lockup' style='font-size: 14px'><%= mark[:fountain] %>fountain</a>
|
|
93
|
+
<a class='lockup plex' href='#lockup'><%= mark[:fountain] %>fountain</a>
|
|
94
|
+
<a class='lockup plex' href='#lockup' style='font-size: 34px'><%= mark[:fountain] %>fountain</a>
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<section class='design-section' id='component'>
|
|
99
|
+
<h2>Components</h2>
|
|
100
|
+
<p class='fg-2'>They are not this gem's. The form that dresses every field, the combobox, the
|
|
101
|
+
6-digit code, the phone field, the dialog, the toasts, the thread of messages and the
|
|
102
|
+
twenty-two Stimulus controllers all live in <strong>bh</strong>, which is Bootstrap 6 for any
|
|
103
|
+
Rails app and has no opinion about this house. This gem depends on it and bundles it, so the
|
|
104
|
+
stylesheet and the script above already carry every one of them — and bh's own page draws each
|
|
105
|
+
beside the line of Ruby that produces it.</p>
|
|
106
|
+
<p class='mt-3'><a href='https://claudiob.github.io/bh/'>Bootstrap Helpers</a></p>
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
<section class='design-section' id='page'>
|
|
110
|
+
<h2>Error pages</h2>
|
|
111
|
+
<ul class='list-unstyled d-flex flex-wrap justify-content-center gap-3'>
|
|
112
|
+
<li><a href='page/400'>400 Bad request</a></li>
|
|
113
|
+
<li><a href='page/404'>404 Not found</a></li>
|
|
114
|
+
<li><a href='page/406-unsupported-browser'>406 Unsupported browser</a></li>
|
|
115
|
+
<li><a href='page/422'>422 Unprocessable</a></li>
|
|
116
|
+
<li><a href='page/500'>500 Server error</a></li>
|
|
117
|
+
</ul>
|
|
118
|
+
<h3 class='h5 design-subsection'>Down</h3>
|
|
119
|
+
<p class='fg-2'>What an app shows while it is down — its own mark in the middle, the house's at
|
|
120
|
+
the foot. The engine answers <code>/down.html</code> with the right one, and Heroku's
|
|
121
|
+
<code>MAINTENANCE_PAGE_URL</code> points here, where the app is not.</p>
|
|
122
|
+
<ul class='list-unstyled d-flex flex-wrap justify-content-center gap-3'>
|
|
123
|
+
<%- mark.each_key do |app| -%>
|
|
124
|
+
<li><a href='down/<%= app %>'><%= app %></a></li>
|
|
125
|
+
<%- end -%>
|
|
126
|
+
</ul>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
<section class='design-section' id='favicon'>
|
|
130
|
+
<h2>Icons</h2>
|
|
131
|
+
<div class='table-responsive'>
|
|
132
|
+
<table class='table table-hover mx-auto' style='max-width: 60rem'>
|
|
133
|
+
<thead><tr><th scope='col'>Logo</th><th scope='col'>App</th><th scope='col'>.svg</th><th scope='col'>-dev.svg</th><th scope='col'>-96.png</th><th scope='col'>-180.png</th><th scope='col'>-192.png</th><th scope='col'>-512.png</th><th scope='col'>.ico</th><th scope='col'>.webmanifest</th></tr></thead>
|
|
134
|
+
<tbody>
|
|
135
|
+
<tr><td><img class='design-logo' src='logo/easyfaa.svg' alt=''></td><th scope='row'>easyfaa</th><td><img src='favicon/easyfaa.svg' width='24' alt=''></td><td><img src='favicon/easyfaa-dev.svg' width='24' alt=''></td><td><img src='favicon/easyfaa-96.png' width='24' alt=''></td><td><img src='favicon/easyfaa-180.png' width='24' alt=''></td><td><img src='favicon/easyfaa-192.png' width='24' alt=''></td><td><img src='favicon/easyfaa-512.png' width='24' alt=''></td><td><a href='favicon/easyfaa.ico'>ico</a></td><td><a href='favicon/easyfaa.webmanifest'>manifest</a></td></tr>
|
|
136
|
+
<tr><td><img class='design-logo' src='logo/cube.svg' alt=''></td><th scope='row'>cube</th><td><img src='favicon/cube.svg' width='24' alt=''></td><td><img src='favicon/cube-dev.svg' width='24' alt=''></td><td><img src='favicon/cube-96.png' width='24' alt=''></td><td><img src='favicon/cube-180.png' width='24' alt=''></td><td><img src='favicon/cube-192.png' width='24' alt=''></td><td><img src='favicon/cube-512.png' width='24' alt=''></td><td><a href='favicon/cube.ico'>ico</a></td><td><a href='favicon/cube.webmanifest'>manifest</a></td></tr>
|
|
137
|
+
<tr><td><img class='design-logo' src='logo/fountain.svg' alt=''></td><th scope='row'>fountain</th><td><img src='favicon/fountain.svg' width='24' alt=''></td><td><img src='favicon/fountain-dev.svg' width='24' alt=''></td><td><img src='favicon/fountain-96.png' width='24' alt=''></td><td><img src='favicon/fountain-180.png' width='24' alt=''></td><td><img src='favicon/fountain-192.png' width='24' alt=''></td><td><img src='favicon/fountain-512.png' width='24' alt=''></td><td><a href='favicon/fountain.ico'>ico</a></td><td><a href='favicon/fountain.webmanifest'>manifest</a></td></tr>
|
|
138
|
+
<tr><td><img class='design-logo' src='logo/goldrush.svg' alt=''></td><th scope='row'>goldrush</th><td><img src='favicon/goldrush.svg' width='24' alt=''></td><td><img src='favicon/goldrush-dev.svg' width='24' alt=''></td><td><img src='favicon/goldrush-96.png' width='24' alt=''></td><td><img src='favicon/goldrush-180.png' width='24' alt=''></td><td><img src='favicon/goldrush-192.png' width='24' alt=''></td><td><img src='favicon/goldrush-512.png' width='24' alt=''></td><td><a href='favicon/goldrush.ico'>ico</a></td><td><a href='favicon/goldrush.webmanifest'>manifest</a></td></tr>
|
|
139
|
+
<tr><td><img class='design-logo' src='logo/houston.svg' alt=''></td><th scope='row'>houston</th><td><img src='favicon/houston.svg' width='24' alt=''></td><td><img src='favicon/houston-dev.svg' width='24' alt=''></td><td><img src='favicon/houston-96.png' width='24' alt=''></td><td><img src='favicon/houston-180.png' width='24' alt=''></td><td><img src='favicon/houston-192.png' width='24' alt=''></td><td><img src='favicon/houston-512.png' width='24' alt=''></td><td><a href='favicon/houston.ico'>ico</a></td><td><a href='favicon/houston.webmanifest'>manifest</a></td></tr>
|
|
140
|
+
<tr><td><img class='design-logo' src='logo/design.svg' alt=''></td><th scope='row'>design</th><td><img src='favicon/design.svg' width='24' alt=''></td><td><img src='favicon/design-dev.svg' width='24' alt=''></td><td><img src='favicon/design-96.png' width='24' alt=''></td><td><img src='favicon/design-180.png' width='24' alt=''></td><td><img src='favicon/design-192.png' width='24' alt=''></td><td><img src='favicon/design-512.png' width='24' alt=''></td><td><a href='favicon/design.ico'>ico</a></td><td><a href='favicon/design.webmanifest'>manifest</a></td></tr>
|
|
141
|
+
<tr><td><img class='design-logo' src='logo/autopilot.svg' alt=''></td><th scope='row'>autopilot</th><td><img src='favicon/autopilot.svg' width='24' alt=''></td><td><img src='favicon/autopilot-dev.svg' width='24' alt=''></td><td><img src='favicon/autopilot-96.png' width='24' alt=''></td><td><img src='favicon/autopilot-180.png' width='24' alt=''></td><td><img src='favicon/autopilot-192.png' width='24' alt=''></td><td><img src='favicon/autopilot-512.png' width='24' alt=''></td><td><a href='favicon/autopilot.ico'>ico</a></td><td><a href='favicon/autopilot.webmanifest'>manifest</a></td></tr>
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
</main>
|
|
147
|
+
|
|
148
|
+
<footer class='container-fluid px-3 py-4 small fg-2 d-flex flex-column align-items-center gap-2'>
|
|
149
|
+
<code>gem install easyfaa</code>
|
|
150
|
+
<div class='d-flex flex-wrap justify-content-center gap-3'>
|
|
151
|
+
<a href='https://github.com/easyfaa/design'>Source</a>
|
|
152
|
+
<a href='https://rubydoc.info/gems/easyfaa'>Reference</a>
|
|
153
|
+
<a href='https://rubygems.org/gems/easyfaa'>RubyGems</a>
|
|
154
|
+
</div>
|
|
155
|
+
</footer>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
data/lib/easyfaa/copy.rb
ADDED
data/lib/easyfaa/down.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'rack/files'
|
|
2
|
+
|
|
3
|
+
module Easyfaa
|
|
4
|
+
# Answers `/down.html` with the page the host shows while it is down: its own where it is
|
|
5
|
+
# one of the family, the house's otherwise.
|
|
6
|
+
class Down
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
@files = Rack::Files.new Engine.root.join('public/down').to_s
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The page where the path asks for it, and the host otherwise.
|
|
13
|
+
def call(env)
|
|
14
|
+
return @app.call env unless env['PATH_INFO'] == '/down.html'
|
|
15
|
+
|
|
16
|
+
@files.call env.merge('PATH_INFO' => "/#{Easyfaa.app}.html")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# rails/engine alone raises NoMethodError: railtie.rb calls delegate_missing_to too early.
|
|
2
|
+
require 'rails'
|
|
3
|
+
|
|
4
|
+
module Easyfaa
|
|
5
|
+
# Hooks the gem into a host app: serves its files, its error pages and its helpers.
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
# The folders the design site publishes, served from this gem at the same paths so a
|
|
8
|
+
# page on an app and a page on the site link one file — this version's copy under its
|
|
9
|
+
# own prefix among them. Each keeps its slash: `Rack::Static` matches on `start_with?`,
|
|
10
|
+
# and `/page` would swallow `/pages` itself.
|
|
11
|
+
PREFIXES = %W[/css/ /js/ /theme/ /font/ /logo/ /favicon/ /page/ /v#{VERSION}/].freeze
|
|
12
|
+
|
|
13
|
+
# An unversioned file is asked for again on every full load and answered `304 Not
|
|
14
|
+
# Modified` while it stands, since its URL never changes between versions. A font, whose
|
|
15
|
+
# name carries a hash of its bytes, and anything under this version's prefix never change
|
|
16
|
+
# at all, so a browser may keep them for good.
|
|
17
|
+
HEADERS = [
|
|
18
|
+
[:all, { 'cache-control' => 'no-cache' }],
|
|
19
|
+
['/font/', { 'cache-control' => 'public, max-age=31536000, immutable' }],
|
|
20
|
+
['/v', { 'cache-control' => 'public, max-age=31536000, immutable' }],
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
# What a file is kept for while the gem is being worked on: not at all, since the
|
|
24
|
+
# version's copy is rewritten under the same name until it is released, and a browser
|
|
25
|
+
# told to keep it for a year would show yesterday's stylesheet on today's page.
|
|
26
|
+
DEVELOPMENT_HEADERS = HEADERS.first 1
|
|
27
|
+
|
|
28
|
+
# Before `Rails::Rack::Logger`, so fetching a stylesheet writes no `Started GET` line, and
|
|
29
|
+
# cascading, so a host route under one of these prefixes still answers.
|
|
30
|
+
initializer 'easyfaa.assets' do |app|
|
|
31
|
+
headers = Rails.env.development? ? DEVELOPMENT_HEADERS : HEADERS
|
|
32
|
+
app.middleware.insert_before Rails::Rack::Logger, Rack::Static,
|
|
33
|
+
urls: PREFIXES, root: Engine.root.join('public').to_s,
|
|
34
|
+
header_rules: headers, cascade: true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The branded error pages, read by status from `public/page` the way Rails reads a host's
|
|
38
|
+
# own `public/`, unless the host has already named an app of its own.
|
|
39
|
+
initializer 'easyfaa.exceptions' do |app|
|
|
40
|
+
app.config.exceptions_app ||=
|
|
41
|
+
ActionDispatch::PublicExceptions.new Engine.root.join('public/page').to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The page a host shows while it is down, at the path Heroku and a reader expect.
|
|
45
|
+
initializer 'easyfaa.down' do |app|
|
|
46
|
+
app.middleware.insert_before Rails::Rack::Logger, Down
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
initializer 'easyfaa.helpers' do
|
|
50
|
+
ActiveSupport.on_load(:action_view) { include Easyfaa::Helpers }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Easyfaa
|
|
2
|
+
module Helpers
|
|
3
|
+
# The page of a signup flow: the house's mark over the wall it stands on, a title, a line,
|
|
4
|
+
# one card, and the way out under it.
|
|
5
|
+
module Flows
|
|
6
|
+
# How wide the card is unless a page says otherwise: as wide as a thread, a table or a
|
|
7
|
+
# contract wants. A page asking one question says a narrower one.
|
|
8
|
+
WIDTH = '75rem'
|
|
9
|
+
|
|
10
|
+
# Draws the page around the card's content: the lockup over what the view said in
|
|
11
|
+
# `content_for` — its `:title`, its `:subtitle`, how wide the card is in `:width` — and
|
|
12
|
+
# the `links` under the card joined by a middot. No subtitle draws no line and no links
|
|
13
|
+
# no foot: a page says only what it has. The mark and the wall are not asked for, being
|
|
14
|
+
# what a house flow is; every other option is an attribute of the column.
|
|
15
|
+
# @param href [String] where the lockup over the page leads.
|
|
16
|
+
# @param links [Array<String>] the way out, and whatever else stands beside it.
|
|
17
|
+
# @param options [Hash] anything else the column carries.
|
|
18
|
+
# @return [String] the page.
|
|
19
|
+
def flow(href: Logos::HOME, links: [], **options, &)
|
|
20
|
+
parts = [
|
|
21
|
+
tag.header(easyfaa_logo(href: href)),
|
|
22
|
+
*easyfaa_flow_words,
|
|
23
|
+
easyfaa_flow_card(content_for(:width), &),
|
|
24
|
+
easyfaa_flow_links(links),
|
|
25
|
+
]
|
|
26
|
+
classes = class_names 'flow', options[:class]
|
|
27
|
+
|
|
28
|
+
tag.div safe_join(parts.compact), class: classes, data: { controller: 'wall' },
|
|
29
|
+
**options.except(:class)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def easyfaa_flow_words
|
|
35
|
+
[tag.h1(content_for(:title)), (tag.p content_for :subtitle if content_for? :subtitle)]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def easyfaa_flow_card(width, &)
|
|
39
|
+
width = width.presence || WIDTH
|
|
40
|
+
style = "--flow-width: #{width}" unless width == WIDTH
|
|
41
|
+
|
|
42
|
+
tag.div capture(&), class: 'flow-card p-3 md:p-4 mx-auto', style:
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def easyfaa_flow_links(pieces)
|
|
46
|
+
pieces = Array(pieces).compact_blank
|
|
47
|
+
return if pieces.empty?
|
|
48
|
+
|
|
49
|
+
tag.footer safe_join(pieces, tag.span('·', class: 'fg-2', aria: { hidden: true }))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Easyfaa
|
|
2
|
+
module Helpers
|
|
3
|
+
# What every easyfaa page puts in its `<head>`.
|
|
4
|
+
module Heads
|
|
5
|
+
# One line per tag, each at the depth a head's children sit at in a layout indented
|
|
6
|
+
# by two: `<html>`, then `<head>`, then these.
|
|
7
|
+
INDENT = "\n "
|
|
8
|
+
|
|
9
|
+
# The stylesheet and the script every page needs, and the icons `app` is known by —
|
|
10
|
+
# this app unless said, since a page a customer reads may wear the house's mark rather
|
|
11
|
+
# than the app's. Both files at this version's own path, which never changes and so is
|
|
12
|
+
# kept for a year. A page refresh Turbo is sent morphs the page in place and keeps the
|
|
13
|
+
# scroll, so an answer landing in a thread leaves the reader where they were.
|
|
14
|
+
def easyfaa_head_tags(app = Easyfaa.app)
|
|
15
|
+
safe_join [
|
|
16
|
+
tag.meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
|
|
17
|
+
tag.meta(name: 'color-scheme', content: 'light dark'),
|
|
18
|
+
tag.meta(name: 'turbo-refresh-method', content: 'morph'),
|
|
19
|
+
tag.meta(name: 'turbo-refresh-scroll', content: 'preserve'),
|
|
20
|
+
easyfaa_layer,
|
|
21
|
+
easyfaa_icons(app),
|
|
22
|
+
], INDENT
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# bh's layer and the house's on top of it, in that order: every rule and every
|
|
26
|
+
# controller here is written against what bh draws, so bh goes first. bh's files are
|
|
27
|
+
# served by its own engine, under a path of its own.
|
|
28
|
+
def easyfaa_layer
|
|
29
|
+
safe_join [
|
|
30
|
+
tag.link(rel: 'stylesheet', href: "#{Bh::Engine::PREFIX}css/bh.css"),
|
|
31
|
+
tag.script('', type: 'module', src: "#{Bh::Engine::PREFIX}js/bh.js"),
|
|
32
|
+
easyfaa_tags,
|
|
33
|
+
], INDENT
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The house's own two files, for a page that already links bh's. At this version's own
|
|
37
|
+
# path, which never changes and so is kept for a year.
|
|
38
|
+
def easyfaa_tags
|
|
39
|
+
safe_join [
|
|
40
|
+
tag.link(rel: 'stylesheet', href: "/v#{VERSION}/css/easyfaa.css"),
|
|
41
|
+
tag.script('', type: 'module', src: "/v#{VERSION}/js/easyfaa.js"),
|
|
42
|
+
], INDENT
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The icons a tab, a home screen and a saved app know `app` by — this app unless said:
|
|
46
|
+
# the SVG, its dev variant while developing, the 96px PNG, the apple touch icon and the
|
|
47
|
+
# web manifest, one to a line.
|
|
48
|
+
def easyfaa_icons(app = Easyfaa.app)
|
|
49
|
+
mark = Rails.env.development? ? "#{app}-dev" : app
|
|
50
|
+
|
|
51
|
+
safe_join [
|
|
52
|
+
tag.link(rel: 'icon', href: "/favicon/#{mark}.svg", type: 'image/svg+xml'),
|
|
53
|
+
tag.link(rel: 'icon', href: "/favicon/#{app}-96.png", type: 'image/png', sizes: '96x96'),
|
|
54
|
+
tag.link(rel: 'apple-touch-icon', href: "/favicon/#{app}-180.png"),
|
|
55
|
+
tag.link(rel: 'manifest', href: "/favicon/#{app}.webmanifest"),
|
|
56
|
+
], INDENT
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# `squish` and `html_safe`, for the marks read once at load, before any Rails has booted.
|
|
2
|
+
require 'active_support/core_ext/string/filters'
|
|
3
|
+
require 'active_support/core_ext/string/output_safety'
|
|
4
|
+
|
|
5
|
+
module Easyfaa
|
|
6
|
+
module Helpers
|
|
7
|
+
# An app's mark and its name, drawn as one word wherever a page signs itself.
|
|
8
|
+
module Logos
|
|
9
|
+
# Where a logo leads unless told otherwise: the house's front door.
|
|
10
|
+
HOME = 'https://easyfaa.com/'
|
|
11
|
+
|
|
12
|
+
# Every drawing the logos are made of, read once from the art the icons render from.
|
|
13
|
+
DRAWINGS = MARKS.keys.to_h do |app|
|
|
14
|
+
drawing = File.read File.expand_path("../../../art/#{app}.svg", __dir__)
|
|
15
|
+
[app, drawing.sub('<svg ', "<svg aria-hidden='true' ").squish.html_safe]
|
|
16
|
+
end.freeze
|
|
17
|
+
|
|
18
|
+
# The mark and the name of `app` — the house's horizon and `easyfaa` by default — as a
|
|
19
|
+
# link to `href`. Every other option is an attribute of the link, `class` included.
|
|
20
|
+
def easyfaa_logo(app: :easyfaa, href: HOME, **options)
|
|
21
|
+
classes = class_names 'lockup plex', options[:class]
|
|
22
|
+
words = safe_join [easyfaa_logo_mark(app), easyfaa_logo_word(app)]
|
|
23
|
+
|
|
24
|
+
tag.a words, class: classes, **options.except(:class), href:
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def easyfaa_logo_mark(app)
|
|
30
|
+
size, shift = MARKS.fetch(app).map { |px| (px / 48).round 4 }
|
|
31
|
+
style = "width: #{size}em; height: #{size}em; top: #{shift}em"
|
|
32
|
+
|
|
33
|
+
DRAWINGS.fetch(app).sub('<svg ', "<svg style='#{style}' ").html_safe
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def easyfaa_logo_word(app)
|
|
37
|
+
return app.to_s unless app == :easyfaa
|
|
38
|
+
|
|
39
|
+
safe_join [tag.span('easy', class: 'easy'), 'faa']
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'bh'
|
|
2
|
+
require_relative 'helpers/flows'
|
|
3
|
+
require_relative 'helpers/heads'
|
|
4
|
+
require_relative 'helpers/logos'
|
|
5
|
+
|
|
6
|
+
module Easyfaa
|
|
7
|
+
# Everything a view may call, in one module the engine puts on every Action View.
|
|
8
|
+
module Helpers
|
|
9
|
+
include Flows, Heads, Logos
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Easyfaa
|
|
2
|
+
# How each app's mark sits beside its name in a lockup, measured at 48px: the box the
|
|
3
|
+
# drawing is rendered at, and how far down it is pushed so its ink stands on the baseline
|
|
4
|
+
# and reaches IBM Plex Sans's ascender. Every drawing is a different shape inside its 512
|
|
5
|
+
# grid, so equal sizes never align; these do. The house's own is the one exception: 0.8em
|
|
6
|
+
# and dropped 0.16em, so the horizon centres on the lowercase rather than standing on it.
|
|
7
|
+
MARKS = {
|
|
8
|
+
autopilot: [46.36, 5.42],
|
|
9
|
+
cube: [38.96, 1.72],
|
|
10
|
+
design: [50.47, 8.94],
|
|
11
|
+
easyfaa: [38.4, 7.68],
|
|
12
|
+
fountain: [39.54, 2.01],
|
|
13
|
+
goldrush: [49.50, 11.02],
|
|
14
|
+
houston: [39.73, 2.11],
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
data/lib/easyfaa.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'bh'
|
|
2
|
+
require_relative 'easyfaa/version'
|
|
3
|
+
require_relative 'easyfaa/marks'
|
|
4
|
+
require_relative 'easyfaa/copy'
|
|
5
|
+
require_relative 'easyfaa/down'
|
|
6
|
+
require_relative 'easyfaa/helpers'
|
|
7
|
+
require_relative 'easyfaa/engine'
|
|
8
|
+
|
|
9
|
+
# easyfaa's front end: the stylesheet and script every page loads — bh's Bootstrap 6
|
|
10
|
+
# with the house's own layer over it — the marks every app is known by, and the helpers that
|
|
11
|
+
# draw a head, a logo or a signup page the same way everywhere.
|
|
12
|
+
module Easyfaa
|
|
13
|
+
# The form every easyfaa page is written with, which is bh's: the house dresses no
|
|
14
|
+
# field differently, so this name and `Bh::FormBuilder` are one builder.
|
|
15
|
+
FormBuilder = Bh::FormBuilder
|
|
16
|
+
|
|
17
|
+
# One message of a chat, which is bh's too. Named here for a host that reached for it by
|
|
18
|
+
# the house's name before the components moved.
|
|
19
|
+
Message = Bh::Message
|
|
20
|
+
|
|
21
|
+
# Which of the family this host is, read off the Rails application's own name — the
|
|
22
|
+
# `Goldrush` of `Goldrush::Application` — and the house itself for any app not in `MARKS`.
|
|
23
|
+
# What a tab wears, what a logo says and whose page answers `/down.html`.
|
|
24
|
+
def self.app
|
|
25
|
+
name = Rails.application.class.module_parent_name.downcase.to_sym
|
|
26
|
+
MARKS.key?(name) ? name : :easyfaa
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The words that carry a 6-digit code to a phone, in both voices. `written` is the sentence
|
|
30
|
+
# a person reads and, under it, the line Safari matches against the page about to be filled,
|
|
31
|
+
# `@host #code`, which offers the code above the keyboard so nobody leaves for Messages to
|
|
32
|
+
# copy it. `spoken` is the sentence alone, its digits one at a time so a listener can write
|
|
33
|
+
# them down, and without a line meant for a phone rather than a person. Every app of the
|
|
34
|
+
# house that sends a code sends these, from `config/locales`.
|
|
35
|
+
def self.one_time_code(pin, host: Rails.application.default_url_options[:host])
|
|
36
|
+
Copy.new written: I18n.t('easyfaa.one_time_code', pin:, host:),
|
|
37
|
+
spoken: I18n.t('easyfaa.spoken_one_time_code', pin: pin.chars.join(', '))
|
|
38
|
+
end
|
|
39
|
+
end
|
data/public/404.html
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>The page you were looking for doesn't exist (404 Not found)</title>
|
|
7
|
+
<meta name="color-scheme" content="light dark">
|
|
8
|
+
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
|
9
|
+
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
|
10
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
11
|
+
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
+
<style>
|
|
13
|
+
@font-face {
|
|
14
|
+
font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 500; font-display: swap;
|
|
15
|
+
src: url('/font/ibm-plex-sans-500-vietnamese.woff2') format('woff2');
|
|
16
|
+
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
17
|
+
}
|
|
18
|
+
@font-face {
|
|
19
|
+
font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 500; font-display: swap;
|
|
20
|
+
src: url('/font/ibm-plex-sans-500-latin-ext.woff2') format('woff2');
|
|
21
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
22
|
+
}
|
|
23
|
+
@font-face {
|
|
24
|
+
font-family: 'IBM Plex Sans'; font-style: normal; font-weight: 500; font-display: swap;
|
|
25
|
+
src: url('/font/ibm-plex-sans-500-latin.woff2') format('woff2');
|
|
26
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
27
|
+
}
|
|
28
|
+
:root {
|
|
29
|
+
--bg: #fbfcfe;
|
|
30
|
+
--ink: #0b1e3a;
|
|
31
|
+
--mark: #5aa9e6;
|
|
32
|
+
--muted: #5c6c84;
|
|
33
|
+
}
|
|
34
|
+
@media (prefers-color-scheme: dark) {
|
|
35
|
+
:root {
|
|
36
|
+
--bg: #0f1622;
|
|
37
|
+
--ink: #cfe3f6;
|
|
38
|
+
--muted: #8698af;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
* { box-sizing: border-box; }
|
|
42
|
+
body {
|
|
43
|
+
margin: 0;
|
|
44
|
+
min-height: 100svh;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
background: var(--bg);
|
|
48
|
+
font-family: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;
|
|
49
|
+
font-weight: 500;
|
|
50
|
+
letter-spacing: 0.01em;
|
|
51
|
+
}
|
|
52
|
+
main {
|
|
53
|
+
flex: 1;
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
padding: 40px 24px;
|
|
59
|
+
text-align: center;
|
|
60
|
+
}
|
|
61
|
+
.lockup {
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
max-width: 100%;
|
|
64
|
+
color: var(--ink);
|
|
65
|
+
line-height: 1;
|
|
66
|
+
margin: 0;
|
|
67
|
+
}
|
|
68
|
+
.lockup svg {
|
|
69
|
+
position: relative;
|
|
70
|
+
vertical-align: baseline;
|
|
71
|
+
}
|
|
72
|
+
main .lockup {
|
|
73
|
+
font-size: clamp(1.5rem, 5.5vw, 2.5rem);
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
/* The longest heading is wider than a phone, so let it wrap rather than
|
|
76
|
+
overflow; the mark stays attached to the status code on the first line. */
|
|
77
|
+
white-space: normal;
|
|
78
|
+
text-wrap: balance;
|
|
79
|
+
line-height: 1.25;
|
|
80
|
+
}
|
|
81
|
+
main .lockup svg {
|
|
82
|
+
width: 1.0073em; height: 1.0073em;
|
|
83
|
+
top: 0.1337em;
|
|
84
|
+
margin-right: 0.1327em;
|
|
85
|
+
}
|
|
86
|
+
main p {
|
|
87
|
+
margin: 22px 0 0;
|
|
88
|
+
color: var(--muted);
|
|
89
|
+
font-size: 1rem;
|
|
90
|
+
line-height: 1.6;
|
|
91
|
+
}
|
|
92
|
+
footer {
|
|
93
|
+
position: sticky;
|
|
94
|
+
bottom: 0;
|
|
95
|
+
background: var(--bg);
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
padding: 20px 0 26px;
|
|
99
|
+
}
|
|
100
|
+
footer .lockup { font-size: 20px; }
|
|
101
|
+
footer .lockup svg {
|
|
102
|
+
width: 16px; height: 16px;
|
|
103
|
+
top: 3.2px;
|
|
104
|
+
margin-right: 4.2px;
|
|
105
|
+
}
|
|
106
|
+
footer .easy { color: var(--mark); }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<main>
|
|
111
|
+
<h1 class="lockup"><svg viewBox="0 0 512 512" aria-hidden="true"><g fill="none" stroke="#5aa9e6" stroke-width="28" stroke-linecap="round"><path d="M251.4 235.6 C254.8 239.8 256.5 246.9 254 254.2 C251.6 261.5 245 268.8 235.4 272.3 C225.8 275.7 213.3 275.1 202.2 268.8 C191 262.6 181.5 250.6 178.1 235.6 C174.6 220.6 177.5 202.7 187.6 187.7 C197.6 172.8 215 161 235.4 157.6 C255.8 154.1 279.1 159.2 297.9 173.1 C316.6 187 330.6 209.8 334.1 235.6 C337.5 261.4 330.2 290.1 312.5 312.7 C294.7 335.3 266.6 351.5 235.4 354.9 C204.2 358.4 170.2 348.8 143.7 327.3 C117.3 305.7 98.8 272.2 95.4 235.6 C92 198.9 103.7 159.6 129.1 129.3 C154.5 99 193.3 78.4 235.4 74.9 C277.5 71.5 322.2 85.5 356.3 114.7 C390.4 143.9 413.3 188.1 416.7 235.6 C420.2 283.1 404 333.2 370.9 371.1 C337.9 409 288.3 434.1 235.4 437.6"/></g></svg>404 Not found</h1>
|
|
112
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
|
113
|
+
</main>
|
|
114
|
+
<footer>
|
|
115
|
+
<p class="lockup"><svg viewBox="0 0 512 512" aria-hidden="true"><g fill="none" stroke="#5aa9e6" stroke-width="28" stroke-linecap="round" stroke-linejoin="round"><path d="M74.5 340H437.5A200 200 0 0 1 74.5 340Z" fill="#0b1e3a" stroke="none"/><circle cx="256" cy="256" r="200"/><path d="M150 256H216L256 296L296 256H362"/></g></svg><span class="easy">easy</span>faa</p>
|
|
116
|
+
</footer>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
data/public/_headers
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# A page on another origin loads the script as a module and the stylesheet's font across
|
|
2
|
+
# origins, both of which the browser refuses without this. Cloudflare Pages adds no CORS.
|
|
3
|
+
/*
|
|
4
|
+
Access-Control-Allow-Origin: *
|
|
5
|
+
|
|
6
|
+
# An unversioned file follows the latest release, so a browser asks for it again on every
|
|
7
|
+
# load and is answered 304 while it stands. Pages would otherwise let one be kept for four
|
|
8
|
+
# hours, and a page drawn against a new bundle would run the old one until they were up.
|
|
9
|
+
/css/*
|
|
10
|
+
Cache-Control: public, max-age=0, must-revalidate
|
|
11
|
+
/js/*
|
|
12
|
+
Cache-Control: public, max-age=0, must-revalidate
|
|
13
|
+
/theme/*
|
|
14
|
+
Cache-Control: public, max-age=0, must-revalidate
|
|
15
|
+
/down/*
|
|
16
|
+
Cache-Control: public, max-age=0, must-revalidate
|
|
17
|
+
/page/*
|
|
18
|
+
Cache-Control: public, max-age=0, must-revalidate
|
|
19
|
+
|
|
20
|
+
# A versioned copy never changes, so a browser may keep it for a year and the edge for good.
|
|
21
|
+
/v*
|
|
22
|
+
Cache-Control: public, max-age=31536000, immutable
|
|
23
|
+
|
|
24
|
+
# A font's name carries a hash of its bytes, so a browser may keep it for good; everything
|
|
25
|
+
# else keeps Pages' default, which is to revalidate on every load and answer 304 while it
|
|
26
|
+
# stands, so the URL of a file never has to change between versions.
|
|
27
|
+
/font/*
|
|
28
|
+
Cache-Control: public, max-age=31536000, immutable
|