wordmark 0.21.0 → 0.23.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.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -550
  3. data/README.md +60 -44
  4. data/app/assets/fonts/easy/outfit-latin-ext.woff2 +0 -0
  5. data/app/assets/fonts/easy/outfit-latin.woff2 +0 -0
  6. data/app/assets/images/easy/sprite.svg +52 -0
  7. data/app/assets/javascripts/easy/flyer.js +100 -0
  8. data/app/assets/javascripts/easy/konami.js +68 -0
  9. data/app/assets/javascripts/easy/modal.js +48 -0
  10. data/app/assets/javascripts/easy/pin_code.js +43 -0
  11. data/app/assets/javascripts/easy/report_sort.js +44 -0
  12. data/app/assets/javascripts/easy/rotating_placeholder.js +35 -0
  13. data/app/assets/stylesheets/easy/base.css +35 -0
  14. data/app/assets/stylesheets/easy/components.css +388 -0
  15. data/app/assets/stylesheets/easy/layout.css +44 -0
  16. data/app/assets/stylesheets/easy/tokens.css +9 -0
  17. data/app/controllers/wordmark/errors_controller.rb +41 -0
  18. data/app/helpers/wordmark/easy_helper.rb +28 -0
  19. data/app/models/aircraft_avatar.rb +59 -0
  20. data/app/views/easy/_flyer.html.erb +10 -0
  21. data/app/views/easy/_modal.html.erb +14 -0
  22. data/app/views/easy/_pin_code.html.erb +9 -0
  23. data/app/views/easy/_report_head.html.erb +19 -0
  24. data/app/views/easy/mailer/_code.html.erb +67 -0
  25. data/app/views/easy/mailer/_code.text.erb +16 -0
  26. data/app/views/layouts/easy/site.html.erb +50 -0
  27. data/app/views/layouts/wordmark/errors.html.erb +26 -0
  28. data/app/views/wordmark/errors/show.html.erb +15 -0
  29. data/art/blackbox.svg +6 -0
  30. data/art/data.svg +8 -0
  31. data/art/dataplate.svg +12 -0
  32. data/art/drive.svg +6 -0
  33. data/art/hangar.svg +6 -0
  34. data/art/index.html.erb +25 -25
  35. data/art/lens.svg +9 -0
  36. data/art/mirroring.svg +8 -0
  37. data/art/refinery.svg +9 -0
  38. data/art/rollaway.svg +12 -0
  39. data/art/stockroom.svg +12 -0
  40. data/art/tower.svg +12 -0
  41. data/art/wordmark.svg +3 -0
  42. data/config/aircraft_avatars.yml +26 -0
  43. data/lib/wordmark/engine.rb +22 -28
  44. data/lib/wordmark/helpers/heads.rb +6 -6
  45. data/lib/wordmark/marks.rb +14 -4
  46. data/lib/wordmark/version.rb +1 -1
  47. data/lib/wordmark.rb +11 -6
  48. data/public/_headers +4 -8
  49. data/public/bh/js/bh.js +15 -15
  50. data/public/down/{goldrush.html → blackbox.html} +5 -5
  51. data/public/down/{fountain.html → data.html} +5 -5
  52. data/public/down/dataplate.html +87 -0
  53. data/public/down/drive.html +87 -0
  54. data/public/down/{houston.html → hangar.html} +5 -5
  55. data/public/down/lens.html +87 -0
  56. data/public/down/{autopilot.html → mirroring.html} +5 -5
  57. data/public/down/refinery.html +87 -0
  58. data/public/down/rollaway.html +87 -0
  59. data/public/down/stockroom.html +87 -0
  60. data/public/down/tower.html +87 -0
  61. data/public/down/wordmark.html +87 -0
  62. data/public/favicon/blackbox-180.png +0 -0
  63. data/public/favicon/blackbox-192.png +0 -0
  64. data/public/favicon/blackbox-512.png +0 -0
  65. data/public/favicon/blackbox-96.png +0 -0
  66. data/public/favicon/blackbox-dev.svg +6 -0
  67. data/public/favicon/blackbox.ico +0 -0
  68. data/public/favicon/blackbox.svg +6 -0
  69. data/public/favicon/blackbox.webmanifest +12 -0
  70. data/public/favicon/data-180.png +0 -0
  71. data/public/favicon/data-192.png +0 -0
  72. data/public/favicon/data-512.png +0 -0
  73. data/public/favicon/data-96.png +0 -0
  74. data/public/favicon/data-dev.svg +8 -0
  75. data/public/favicon/data.ico +0 -0
  76. data/public/favicon/data.svg +8 -0
  77. data/public/favicon/data.webmanifest +12 -0
  78. data/public/favicon/dataplate-180.png +0 -0
  79. data/public/favicon/dataplate-192.png +0 -0
  80. data/public/favicon/dataplate-512.png +0 -0
  81. data/public/favicon/dataplate-96.png +0 -0
  82. data/public/favicon/dataplate-dev.svg +12 -0
  83. data/public/favicon/dataplate.ico +0 -0
  84. data/public/favicon/dataplate.svg +12 -0
  85. data/public/favicon/dataplate.webmanifest +12 -0
  86. data/public/favicon/drive-180.png +0 -0
  87. data/public/favicon/drive-192.png +0 -0
  88. data/public/favicon/drive-512.png +0 -0
  89. data/public/favicon/drive-96.png +0 -0
  90. data/public/favicon/drive-dev.svg +6 -0
  91. data/public/favicon/drive.ico +0 -0
  92. data/public/favicon/drive.svg +6 -0
  93. data/public/favicon/drive.webmanifest +12 -0
  94. data/public/favicon/hangar-180.png +0 -0
  95. data/public/favicon/hangar-192.png +0 -0
  96. data/public/favicon/hangar-512.png +0 -0
  97. data/public/favicon/hangar-96.png +0 -0
  98. data/public/favicon/hangar-dev.svg +6 -0
  99. data/public/favicon/hangar.ico +0 -0
  100. data/public/favicon/hangar.svg +6 -0
  101. data/public/favicon/hangar.webmanifest +12 -0
  102. data/public/favicon/lens-180.png +0 -0
  103. data/public/favicon/lens-192.png +0 -0
  104. data/public/favicon/lens-512.png +0 -0
  105. data/public/favicon/lens-96.png +0 -0
  106. data/public/favicon/lens-dev.svg +9 -0
  107. data/public/favicon/lens.ico +0 -0
  108. data/public/favicon/lens.svg +9 -0
  109. data/public/favicon/lens.webmanifest +12 -0
  110. data/public/favicon/mirroring-180.png +0 -0
  111. data/public/favicon/mirroring-192.png +0 -0
  112. data/public/favicon/mirroring-512.png +0 -0
  113. data/public/favicon/mirroring-96.png +0 -0
  114. data/public/favicon/mirroring-dev.svg +8 -0
  115. data/public/favicon/mirroring.ico +0 -0
  116. data/public/favicon/mirroring.svg +8 -0
  117. data/public/favicon/mirroring.webmanifest +12 -0
  118. data/public/favicon/refinery-180.png +0 -0
  119. data/public/favicon/refinery-192.png +0 -0
  120. data/public/favicon/refinery-512.png +0 -0
  121. data/public/favicon/refinery-96.png +0 -0
  122. data/public/favicon/refinery-dev.svg +9 -0
  123. data/public/favicon/refinery.ico +0 -0
  124. data/public/favicon/refinery.svg +9 -0
  125. data/public/favicon/refinery.webmanifest +12 -0
  126. data/public/favicon/rollaway-180.png +0 -0
  127. data/public/favicon/rollaway-192.png +0 -0
  128. data/public/favicon/rollaway-512.png +0 -0
  129. data/public/favicon/rollaway-96.png +0 -0
  130. data/public/favicon/rollaway-dev.svg +12 -0
  131. data/public/favicon/rollaway.ico +0 -0
  132. data/public/favicon/rollaway.svg +12 -0
  133. data/public/favicon/rollaway.webmanifest +12 -0
  134. data/public/favicon/stockroom-180.png +0 -0
  135. data/public/favicon/stockroom-192.png +0 -0
  136. data/public/favicon/stockroom-512.png +0 -0
  137. data/public/favicon/stockroom-96.png +0 -0
  138. data/public/favicon/stockroom-dev.svg +12 -0
  139. data/public/favicon/stockroom.ico +0 -0
  140. data/public/favicon/stockroom.svg +12 -0
  141. data/public/favicon/stockroom.webmanifest +12 -0
  142. data/public/favicon/tower-180.png +0 -0
  143. data/public/favicon/tower-192.png +0 -0
  144. data/public/favicon/tower-512.png +0 -0
  145. data/public/favicon/tower-96.png +0 -0
  146. data/public/favicon/tower-dev.svg +12 -0
  147. data/public/favicon/tower.ico +0 -0
  148. data/public/favicon/tower.svg +12 -0
  149. data/public/favicon/tower.webmanifest +12 -0
  150. data/public/favicon/wordmark-180.png +0 -0
  151. data/public/favicon/wordmark-192.png +0 -0
  152. data/public/favicon/wordmark-512.png +0 -0
  153. data/public/favicon/wordmark-96.png +0 -0
  154. data/public/favicon/wordmark-dev.svg +3 -0
  155. data/public/favicon/wordmark.ico +0 -0
  156. data/public/favicon/wordmark.svg +3 -0
  157. data/public/favicon/wordmark.webmanifest +12 -0
  158. data/public/index.html +49 -33
  159. data/public/logo/blackbox.png +0 -0
  160. data/public/logo/blackbox.svg +6 -0
  161. data/public/logo/data.png +0 -0
  162. data/public/logo/data.svg +8 -0
  163. data/public/logo/dataplate.png +0 -0
  164. data/public/logo/dataplate.svg +12 -0
  165. data/public/logo/drive.png +0 -0
  166. data/public/logo/drive.svg +6 -0
  167. data/public/logo/hangar.png +0 -0
  168. data/public/logo/hangar.svg +6 -0
  169. data/public/logo/lens.png +0 -0
  170. data/public/logo/lens.svg +9 -0
  171. data/public/logo/mirroring.png +0 -0
  172. data/public/logo/mirroring.svg +8 -0
  173. data/public/logo/refinery.png +0 -0
  174. data/public/logo/refinery.svg +9 -0
  175. data/public/logo/rollaway.png +0 -0
  176. data/public/logo/rollaway.svg +12 -0
  177. data/public/logo/stockroom.png +0 -0
  178. data/public/logo/stockroom.svg +12 -0
  179. data/public/logo/tower.png +0 -0
  180. data/public/logo/tower.svg +12 -0
  181. data/public/logo/wordmark.png +0 -0
  182. data/public/logo/wordmark.svg +3 -0
  183. metadata +173 -72
  184. data/art/autopilot.svg +0 -9
  185. data/art/fountain.svg +0 -9
  186. data/art/goldrush.svg +0 -7
  187. data/art/houston.svg +0 -8
  188. data/lib/wordmark/down.rb +0 -19
  189. data/public/favicon/autopilot-180.png +0 -0
  190. data/public/favicon/autopilot-192.png +0 -0
  191. data/public/favicon/autopilot-512.png +0 -0
  192. data/public/favicon/autopilot-96.png +0 -0
  193. data/public/favicon/autopilot-dev.svg +0 -9
  194. data/public/favicon/autopilot.ico +0 -0
  195. data/public/favicon/autopilot.svg +0 -9
  196. data/public/favicon/autopilot.webmanifest +0 -12
  197. data/public/favicon/fountain-180.png +0 -0
  198. data/public/favicon/fountain-192.png +0 -0
  199. data/public/favicon/fountain-512.png +0 -0
  200. data/public/favicon/fountain-96.png +0 -0
  201. data/public/favicon/fountain-dev.svg +0 -9
  202. data/public/favicon/fountain.ico +0 -0
  203. data/public/favicon/fountain.svg +0 -9
  204. data/public/favicon/fountain.webmanifest +0 -12
  205. data/public/favicon/goldrush-180.png +0 -0
  206. data/public/favicon/goldrush-192.png +0 -0
  207. data/public/favicon/goldrush-512.png +0 -0
  208. data/public/favicon/goldrush-96.png +0 -0
  209. data/public/favicon/goldrush-dev.svg +0 -7
  210. data/public/favicon/goldrush.ico +0 -0
  211. data/public/favicon/goldrush.svg +0 -7
  212. data/public/favicon/goldrush.webmanifest +0 -12
  213. data/public/favicon/houston-180.png +0 -0
  214. data/public/favicon/houston-192.png +0 -0
  215. data/public/favicon/houston-512.png +0 -0
  216. data/public/favicon/houston-96.png +0 -0
  217. data/public/favicon/houston-dev.svg +0 -8
  218. data/public/favicon/houston.ico +0 -0
  219. data/public/favicon/houston.svg +0 -8
  220. data/public/favicon/houston.webmanifest +0 -12
  221. data/public/logo/autopilot.png +0 -0
  222. data/public/logo/autopilot.svg +0 -9
  223. data/public/logo/fountain.png +0 -0
  224. data/public/logo/fountain.svg +0 -9
  225. data/public/logo/goldrush.png +0 -0
  226. data/public/logo/goldrush.svg +0 -7
  227. data/public/logo/houston.png +0 -0
  228. data/public/logo/houston.svg +0 -8
  229. data/public/v0.21.0/bh/css/bh.css +0 -1
  230. data/public/v0.21.0/bh/js/bh.js +0 -152
  231. data/public/v0.21.0/bh/theme/bootstrap.css +0 -10
  232. data/public/v0.21.0/bh/theme/dawn.css +0 -189
  233. data/public/v0.21.0/bh/theme/dracula.css +0 -189
  234. data/public/v0.21.0/bh/theme/gruvbox.css +0 -189
  235. data/public/v0.21.0/bh/theme/monokai.css +0 -190
  236. data/public/v0.21.0/bh/theme/nord.css +0 -189
  237. data/public/v0.21.0/bh/theme/one_dark.css +0 -189
  238. data/public/v0.21.0/bh/theme/solarized.css +0 -188
  239. data/public/v0.21.0/bh/theme/tokyo_night.css +0 -189
  240. data/public/v0.21.0/css/easyfaa.css +0 -1
  241. data/public/v0.21.0/font/bootstrap-icons-FRBPO3H7.woff2 +0 -0
  242. data/public/v0.21.0/font/ibm-plex-sans-500-latin-LA7QXWWL.woff2 +0 -0
  243. data/public/v0.21.0/font/ibm-plex-sans-500-latin-ext-X4QHZICY.woff2 +0 -0
  244. data/public/v0.21.0/font/ibm-plex-sans-500-latin-ext.woff2 +0 -0
  245. data/public/v0.21.0/font/ibm-plex-sans-500-latin.woff2 +0 -0
  246. data/public/v0.21.0/font/ibm-plex-sans-500-vietnamese-24B6BSND.woff2 +0 -0
  247. data/public/v0.21.0/font/ibm-plex-sans-500-vietnamese.woff2 +0 -0
  248. data/public/v0.21.0/js/easyfaa.js +0 -1
data/README.md CHANGED
@@ -16,7 +16,7 @@ Published three ways from this repo:
16
16
 
17
17
  | Surface | Where | What it is for |
18
18
  | --- | --- | --- |
19
- | Site | https://design.easyfaa.com (staging: https://design.houseparty.dev) | A stable URL for every asset, and a showcase of them |
19
+ | Site | https://design.easyfaa.com | A stable URL for every asset, and a showcase of them |
20
20
  | Gem | `wordmark` on RubyGems | A Rails engine: serves the same tree from the gem, and the helpers |
21
21
  | npm | `easyfaa` | The same built stylesheet and script, for any CDN that carries it |
22
22
 
@@ -63,24 +63,52 @@ after it, and nothing else. A host wanting more copies
63
63
  it into its own `app/views/layouts/`, which wins on being earlier in the view paths.
64
64
 
65
65
  Which of the family an app is, the gem reads off the Rails application's own name: the
66
- `Goldrush` of `Goldrush::Application`. That is what its tab wears, what `easyfaa_head_tags`
67
- links without an argument, and whose page the engine answers `/down.html` with — the app's
68
- mark in the middle and the house's at the foot. An app the family has no mark for wears the
69
- horizon. The same page stands at `https://design.easyfaa.com/down/fountain` for Heroku's
70
- `MAINTENANCE_PAGE_URL`, which has to be somewhere the app is not — without the `.html`,
71
- which Pages redirects away from.
66
+ `Tower` of `Tower::Application`. That is what its tab wears and what `easyfaa_head_tags`
67
+ links without an argument. An app the family has no mark for wears the horizon. A page per
68
+ app stands at `https://design.easyfaa.com/down/tower` for Heroku's `MAINTENANCE_PAGE_URL`,
69
+ which has to be somewhere the app is not.
72
70
 
73
71
  With the gem in the bundle an app serves `/css/easyfaa.css`, `/js/easyfaa.js`,
74
- `/theme/*`, `/font/*`, `/logo/*` and `/favicon/*` itself — and its own version's copy under
75
- `/v0.15.0/`, which is what the head helper links, cached for a year — answers a 400, 404, 406,
76
- 422 or 500 with the house's own page, and answers `/down.html` with this app's.
72
+ `/theme/*`, `/font/*`, `/logo/*` and `/favicon/*` itself, answers a 400, 404, 422 or 500 with
73
+ the same page as every app, and answers `/down.html` with the page it shows while it is down.
74
+
75
+ ## The easy design system
76
+
77
+ What every easyFAA app's pages are drawn with. The stylesheets, scripts, fonts and icons are
78
+ under `app/assets`, which the host's asset pipeline reads as its own:
79
+
80
+ ```erb
81
+ <%# The app's own layout fills the slots, then renders the shared page. %>
82
+ <% content_for :easy_head do %><title>easyFAA</title><% end %>
83
+ <% content_for :site_brand, "easyFAA" %>
84
+ <% content_for :site_nav do %><%= link_to "Sign in", sign_in_path %><% end %>
85
+ <%= javascript_include_tag "easy/modal", "easy/pin_code", defer: true %>
86
+ <%= render template: "layouts/easy/site" %>
87
+ ```
88
+
89
+ `layouts/easy/site` links `easy/tokens`, `easy/base`, `easy/layout` and `easy/components` and
90
+ yields `:easy_head`, `:easy_stylesheets`, `:body_class`, `:site_brand_url`, `:site_brand`,
91
+ `:site_nav`, `:sky_band`, `:page_class`, `:site_footer` and `:body_end`. The partials are
92
+ `easy/pin_code`, `easy/modal` (a layout), `easy/flyer`, `easy/report_head` and
93
+ `easy/mailer/code`; the scripts beside them are in `app/assets/javascripts/easy`.
94
+
95
+ `easy_icon "plane"` draws an icon from the sprite every page shares (`easy_icon_href` is its
96
+ address), and `easy_aircraft_avatar AircraftAvatar.for(...)` an aircraft's silhouette until it
97
+ has a picture.
98
+
99
+ The error pages name easyFAA, or the site an app names for the request:
100
+
101
+ ```ruby
102
+ Wordmark.site = ->(request) { ['easyAnnual', 'https://easyannual.com/'] }
103
+ ```
104
+
105
+ The dummy app's `/guide` is the style guide: every token and component, drawn, with its markup.
77
106
 
78
107
  ## Which app this is
79
108
 
80
- Read off the Rails application's own name, the `Goldrush` of `Goldrush::Application`. That is
81
- what its tab wears, what `easyfaa_head_tags` links without an argument, and whose page the
82
- engine answers `/down.html` with — the app's mark in the middle and the house's at the foot. An
83
- app the family has no mark for wears the horizon.
109
+ Read off the Rails application's own name, the `Tower` of `Tower::Application`. That is
110
+ what its tab wears and what `easyfaa_head_tags` links without an argument. An app the family
111
+ has no mark for wears the horizon.
84
112
 
85
113
  ## The helpers
86
114
 
@@ -98,10 +126,10 @@ app the family has no mark for wears the horizon.
98
126
  differently, so equal sizes never align: every mark is rendered at a box of its own and
99
127
  pushed down by its own nudge, and its ink then stands on the baseline. %>
100
128
  <%= easyfaa_logo %>
101
- <%= easyfaa_logo app: :fountain, href: root_path, class: 'fs-3' %>
129
+ <%= easyfaa_logo app: :tower, href: root_path, class: 'fs-3' %>
102
130
 
103
131
  <%# An asset on the design site, for a page this app does not serve — a mail, say. %>
104
- <%= image_tag easyfaa_url('logo/fountain.png') %>
132
+ <%= image_tag easyfaa_url('logo/tower.png') %>
105
133
  ```
106
134
 
107
135
  ## The layout
@@ -181,24 +209,16 @@ A page not on the gem links the two files from the site, and nothing else:
181
209
  <script type='module' src='https://design.easyfaa.com/js/easyfaa.js'></script>
182
210
  ```
183
211
 
184
- Those follow the latest release. Every release is also kept whole under its own version,
185
- forever and cached for a year, for a page that would rather move on its own terms:
212
+ Those follow the latest release. bh's layer goes under the house's, from the same place, since
213
+ the house is drawn on top of it and neither stands alone:
186
214
 
187
215
  ```html
188
- <link rel='stylesheet' href='https://design.easyfaa.com/v0.20.0/css/easyfaa.css'>
189
- <script type='module' src='https://design.easyfaa.com/v0.20.0/js/easyfaa.js'></script>
190
- ```
191
-
192
- bh's layer goes under the house's, from the same place, since the house is drawn on top of it
193
- and neither stands alone:
194
-
195
- ```html
196
- <link rel='stylesheet' href='https://design.easyfaa.com/v0.20.0/bh/css/bh.css'>
197
- <link rel='stylesheet' href='https://design.easyfaa.com/v0.20.0/css/easyfaa.css'>
216
+ <link rel='stylesheet' href='https://design.easyfaa.com/bh/css/bh.css'>
217
+ <link rel='stylesheet' href='https://design.easyfaa.com/css/easyfaa.css'>
198
218
  ```
199
219
 
200
220
  The palettes are beside it, at `bh/theme/nord.css` and the rest — bh's nine, copied out of the
201
- gem at build time. A version under `v` is never written again, so that URL is owed its bytes.
221
+ gem at build time.
202
222
 
203
223
  A host with a Content Security Policy allows whichever origin it names for `style-src`,
204
224
  `script-src` and `font-src`.
@@ -235,22 +255,18 @@ serves them, and `spec.files` is `git ls-files`. `rake assets` rebuilds and fail
235
255
  result is committed, so a source edit cannot ship without its build.
236
256
 
237
257
  While the gem is read from a path, an app in development serves every file `no-cache`, so a
238
- rebuild shows on the next reload; only a production app keeps the versioned copy for a year.
239
-
240
- Every build also writes `public/v<version>/`, whole, with every URL inside pointing under
241
- that prefix, until the tag `v<version>` exists; from then on the folder is never written
242
- again, and a page that pinned it is owed the same bytes for good.
258
+ rebuild shows on the next reload, and so does an app in production: only a font is kept.
243
259
 
244
- A release, in order: `CHANGELOG.md`, the version in `lib/easyfaa/version.rb` and
245
- `package.json`, `bundle exec rake` so the versioned copy is the release's, then commit, tag
246
- `v<version>`, `gem build` and `gem push`, and fast-forward `production` to `main` so the
247
- site carries both the latest and the versioned copy.
260
+ A release, in order: `CHANGELOG.md`, the version in `lib/wordmark/version.rb` and
261
+ `package.json`, `bundle exec rake`, then commit, tag `v<version>`, `gem build` and `gem push`,
262
+ and fast-forward `production` to `main` so the site carries it.
248
263
 
249
264
  ## Cloudflare
250
265
 
251
- The site is a Cloudflare Pages project named `design` on this repo: production branch
252
- `production`, no build command, output directory `public`, custom domain
253
- `design.easyfaa.com`. `main` deploys as the branch alias `main.design.pages.dev`, which
254
- `design.houseparty.dev` is a CNAME to. `public/_headers` adds the CORS header a cross-origin
255
- module script and font need, and marks the hashed fonts immutable; everything else keeps
256
- Pages' default of revalidating on every load, so a URL never has to change between versions.
266
+ The site is a Worker named `design` that serves `public/` as static assets at the custom
267
+ domain `design.easyfaa.com`, as `wrangler.jsonc` says. Workers Builds deploys the `production`
268
+ branch with `npx wrangler deploy` and no build command: what is committed is what is served.
269
+ A path with no file is answered with `404.html` and a 404. `public/_headers` adds the CORS
270
+ header a cross-origin module script and font need, and marks the hashed fonts immutable;
271
+ everything else keeps Cloudflare's default of revalidating on every load, so a URL never has
272
+ to change between versions.
@@ -0,0 +1,52 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <symbol id="i-check" viewBox="0 0 16 16"><circle cx="8" cy="8" r="8" fill="currentColor"/><path d="M4.5 8.2l2.3 2.3 4.7-4.9" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></symbol>
3
+ <symbol id="i-applies" viewBox="0 0 16 16"><circle cx="8" cy="8" r="8" fill="currentColor"/><path d="M8 4.2v4.6" stroke="#fff" stroke-width="2" stroke-linecap="round"/><circle cx="8" cy="11.6" r="1.2" fill="#fff"/></symbol>
4
+ <symbol id="i-maybe" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="2"/><path d="M8 4.2v4.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="8" cy="11.6" r="1.2" fill="currentColor"/></symbol>
5
+ <symbol id="i-none" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="2"/><path d="M4.8 8h6.4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></symbol>
6
+ <symbol id="i-alert" viewBox="0 0 16 16"><path d="M8 1.6L14.8 13.8H1.2Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M8 6v3.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="8" cy="11.6" r="1" fill="currentColor"/></symbol>
7
+ <symbol id="i-superseded" viewBox="0 0 16 16"><path d="M3.5 1.75h5.75l3.25 3.25v9.25h-9z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M9.25 1.75v3.25h3.25" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M1.5 14.5L14.5 1.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
8
+ <symbol id="i-engine" viewBox="0 0 24 24"><path fill="currentColor" d="M7 4v2h3v2H7l-2 2v3H3v-3H1v8h2v-3h2v3h3l2 2h8v-4h2v3h3V9h-3v3h-2V8h-6V6h3V4z"/></symbol>
9
+ <symbol id="i-propeller" viewBox="0 0 16 16"><g fill="currentColor"><path d="M8 7.4C5.4 6 5.3 2.1 8 .5c2.7 1.6 2.6 5.5 0 6.9z"/><path d="M8 7.4C5.4 6 5.3 2.1 8 .5c2.7 1.6 2.6 5.5 0 6.9z" transform="rotate(120 8 8)"/><path d="M8 7.4C5.4 6 5.3 2.1 8 .5c2.7 1.6 2.6 5.5 0 6.9z" transform="rotate(240 8 8)"/><circle cx="8" cy="8" r="2.1"/></g></symbol>
10
+ <symbol id="i-appliance" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M13.07 6.45L15.31 6.84L15.31 9.16L13.07 9.55L12.68 10.49L13.99 12.35L12.35 13.99L10.49 12.68L9.55 13.07L9.16 15.31L6.84 15.31L6.45 13.07L5.51 12.68L3.65 13.99L2.01 12.35L3.32 10.49L2.93 9.55L0.69 9.16L0.69 6.84L2.93 6.45L3.32 5.51L2.01 3.65L3.65 2.01L5.51 3.32L6.45 2.93L6.84 0.69L9.16 0.69L9.55 2.93L10.49 3.32L12.35 2.01L13.99 3.65L12.68 5.51Z M8 5.6a2.4 2.4 0 1 0 0 4.8a2.4 2.4 0 1 0 0-4.8Z"/></symbol>
11
+ <symbol id="i-calendar" viewBox="0 0 16 16"><rect x="2" y="3" width="12" height="11" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M2 6.5h12M5 1.5v3M11 1.5v3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></symbol>
12
+ <symbol id="i-newest" viewBox="0 0 16 16"><path d="M4 2.5v10M1.5 10l2.5 2.5L6.5 10M9 4h5.5M9 7.5h4M9 11h2.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
13
+ <symbol id="i-oldest" viewBox="0 0 16 16"><path d="M4 13.5v-10M1.5 6L4 3.5 6.5 6M9 4h2.5M9 7.5h4M9 11h5.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></symbol>
14
+ <symbol id="i-search" viewBox="0 0 24 24"><circle cx="10.5" cy="10.5" r="6.5" fill="none" stroke="currentColor" stroke-width="2.4"/><path d="M15.5 15.5l5 5" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></symbol>
15
+ <symbol id="i-bookmark" viewBox="0 0 16 16"><path d="M4 1.5h8a.5.5 0 0 1 .5.5v12.3L8 11.2l-4.5 3.1V2a.5.5 0 0 1 .5-.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></symbol>
16
+ <symbol id="i-share" viewBox="0 0 16 16"><circle cx="12" cy="3.5" r="2" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="4" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.5"/><circle cx="12" cy="12.5" r="2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M5.8 7l4.4-2.5M5.8 9l4.4 2.5" stroke="currentColor" stroke-width="1.5"/></symbol>
17
+ <symbol id="i-link" viewBox="0 0 16 16"><path d="M6.6 9.4l2.8-2.8M7 4.6l1.2-1.2a2.8 2.8 0 0 1 4 4L11 8.6M9 11.4l-1.2 1.2a2.8 2.8 0 0 1-4-4L5 7.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></symbol>
18
+ <symbol id="i-out" viewBox="0 0 16 16"><path d="M9 2.5h4.5V7M13.5 2.5L7.5 8.5M11.5 9.5v3a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1h3" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></symbol>
19
+ <symbol id="i-sun" viewBox="0 0 16 16"><circle cx="8" cy="8" r="3.2" fill="currentColor"/><path d="M8 .8v2M8 13.2v2M.8 8h2M13.2 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M2.9 13.1l1.4-1.4M11.7 4.3l1.4-1.4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></symbol>
20
+ <symbol id="i-moon" viewBox="0 0 16 16"><path d="M13.5 10.2A6 6 0 0 1 5.8 2.5a6 6 0 1 0 7.7 7.7z" fill="currentColor"/></symbol>
21
+ <symbol id="i-monitor" viewBox="0 0 16 16"><rect x="1.2" y="2" width="13.6" height="9" rx="1.4" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 14.2h5M8 11v3.2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></symbol>
22
+ <symbol id="i-plane" viewBox="0 0 24 24"><path d="M21 15.5v-2l-8-5V3.2a1.5 1.5 0 0 0-3 0V8.5l-8 5v2l8-2.5v5.2l-2 1.5V21l3.5-1 3.5 1v-1.3l-2-1.5V13z" fill="currentColor"/></symbol>
23
+ <symbol id="i-small-plane" viewBox="0 0 24 24"><path fill="currentColor" d="M8.2 1.1h7.6v1.1H8.2zM12 2.2c1.1 0 1.7 1.1 1.7 2.7v3.2h8.1c.5 0 .9.4.9.9v1.7c0 .5-.4.9-.9.9h-8.1l-.8 7.1h2.9c.5 0 .8.3.8.8v1c0 .5-.3.8-.8.8H8.2c-.5 0-.8-.3-.8-.8v-1c0-.5.3-.8.8-.8h2.9l-.8-7.1H2.2c-.5 0-.9-.4-.9-.9V9c0-.5.4-.9.9-.9h8.1V4.9c0-1.6.6-2.7 1.7-2.7z"/></symbol>
24
+ <symbol id="i-twin-plane" viewBox="0 0 24 24"><path fill="currentColor" d="M12 1.8c.9 0 1.5 1 1.5 2.6v13.4l-.5 2.8h-2l-.5-2.8V4.4c0-1.6.6-2.6 1.5-2.6zM1.3 10.2h21.4c.5 0 .8.3.8.8v1.4c0 .5-.3.8-.8.8H1.3c-.5 0-.8-.3-.8-.8V11c0-.5.3-.8.8-.8zM7 6.2c.7 0 1.2.5 1.2 1.2v7H5.8v-7c0-.7.5-1.2 1.2-1.2zM17 6.2c.7 0 1.2.5 1.2 1.2v7h-2.4v-7c0-.7.5-1.2 1.2-1.2zM4.8 5h4.4v1H4.8zM14.8 5h4.4v1h-4.4zM8.2 19.2h7.6c.3 0 .5.2.5.5v1c0 .3-.2.5-.5.5H8.2c-.3 0-.5-.2-.5-.5v-1c0-.3.2-.5.5-.5z"/></symbol>
25
+ <symbol id="s-x" viewBox="0 0 24 24"><path fill="currentColor" d="M17.8 3h3.1l-6.8 7.8L22 21h-6.2l-4.9-6.4L5.3 21H2.2l7.3-8.3L1.9 3h6.3l4.4 5.9zm-1.1 16.2h1.7L7.4 4.7H5.6z"/></symbol>
26
+ <symbol id="s-fb" viewBox="0 0 24 24"><path fill="currentColor" d="M13.6 22v-8.2h2.8l.4-3.3h-3.2V8.4c0-.9.3-1.6 1.6-1.6h1.7V3.9c-.3 0-1.3-.1-2.5-.1-2.5 0-4.2 1.5-4.2 4.3v2.4H7.4v3.3h2.8V22z"/></symbol>
27
+ <symbol id="s-li" viewBox="0 0 24 24"><path fill="currentColor" d="M3.6 8.6h3.7V20.5H3.6zM5.45 2.8a2.15 2.15 0 110 4.3 2.15 2.15 0 010-4.3zM9.6 8.6h3.5v1.6h.1c.5-.9 1.7-1.9 3.5-1.9 3.8 0 4.5 2.5 4.5 5.7v6.5h-3.7v-5.8c0-1.4 0-3.2-1.9-3.2s-2.3 1.5-2.3 3.1v5.9H9.6z"/></symbol>
28
+ <symbol id="s-rd" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M19.7 5.3a1.6 1.6 0 11-1.5 2l-3.3-.7-1 4.4c2.2.1 4.2.8 5.6 1.9a2.2 2.2 0 113 3 4.3 4.3 0 010 .6c0 3.4-4 6.1-8.5 6.1S5.5 19.9 5.5 16.5v-.6a2.2 2.2 0 113-3c1.4-1 3.3-1.7 5.4-1.8l1.2-5.3 4 .9a1.6 1.6 0 01.6-1.4zM9.3 14.2a1.4 1.4 0 100 2.8 1.4 1.4 0 000-2.8zm5.4 0a1.4 1.4 0 100 2.8 1.4 1.4 0 000-2.8zm-5.2 4.3a.5.5 0 00-.3.9c.9.7 2 1 2.8 1s1.9-.3 2.8-1a.5.5 0 10-.6-.8c-.6.5-1.4.8-2.2.8s-1.6-.3-2.2-.8a.5.5 0 00-.3-.1z"/></symbol>
29
+ <symbol id="s-wa" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a10 10 0 00-8.6 15.1L2 22l5-1.3A10 10 0 1012 2zm0 1.8a8.2 8.2 0 11-4.2 15.3l-.3-.2-3 .8.8-2.9-.2-.3A8.2 8.2 0 0112 3.8zM8.6 7.2c-.2 0-.5 0-.8.3-.3.3-1 1-1 2.5s1.1 2.9 1.2 3.1c.2.2 2.1 3.3 5.2 4.5 2.6 1 3.1.8 3.7.8.6-.1 1.8-.7 2-1.5.3-.7.3-1.3.2-1.5l-.6-.4-2-1c-.3-.1-.5-.1-.7.1l-.9 1.2c-.2.2-.3.2-.6.1-.3-.2-1.3-.5-2.4-1.5-.9-.8-1.5-1.8-1.7-2.1-.2-.3 0-.5.1-.6l.5-.6.3-.5v-.5l-.9-2.3c-.2-.6-.5-.5-.7-.5z"/></symbol>
30
+ <symbol id="s-em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" d="M3 5.5h18v13H3zM3.5 6l8.5 7 8.5-7"/></symbol>
31
+ <!-- Aircraft avatars (a-): side views, nose right, drones from above; holes take the avatar background. -->
32
+ <symbol id="a-high-wing" viewBox="1 1 46 46" fill="currentColor"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L35 18.6H24.5L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M34.2 19.3L37.6 21H33.8V19.3ZM25.2 19.3H32.8V21H25.2Z" style="fill:var(--avatar-bg,#fff)"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="21.5" y="16.6" width="13.0" height="2.0" rx="0.8"/><line x1="31" y1="18.4" x2="27" y2="26.4" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="38.5" y1="27" x2="38.5" y2="32.2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="38.5" cy="33.6" r="2"/><line x1="28" y1="27" x2="29" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="29" cy="33.6" r="2.3"/></symbol>
33
+ <symbol id="a-high-wing-tailwheel" viewBox="1 1 46 46" fill="currentColor"><g transform="rotate(-8 24 28)"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L35 18.6H24.5L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M34.2 19.3L37.6 21H33.8V19.3ZM25.2 19.3H32.8V21H25.2Z" style="fill:var(--avatar-bg,#fff)"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="21.5" y="16.6" width="13.0" height="2.0" rx="0.8"/><line x1="31" y1="18.4" x2="27" y2="26.4" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="32.5" y1="27" x2="34" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="34" cy="33.6" r="2.3"/><line x1="7.5" y1="23.8" x2="7" y2="25.6" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><circle cx="7" cy="26.2" r="1.1"/></g></symbol>
34
+ <symbol id="a-low-wing" viewBox="1 1 46 46" fill="currentColor"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L35 18.6H24.5L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M34.2 19.3L37.6 21H33.8V19.3ZM25.2 19.3H32.8V21H25.2Z" style="fill:var(--avatar-bg,#fff)"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="20" y="26.4" width="14.5" height="2.0" rx="0.8"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="38.5" y1="27" x2="38.5" y2="32.2" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="38.5" cy="33.6" r="2"/><line x1="28" y1="27" x2="29" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="29" cy="33.6" r="2.3"/></symbol>
35
+ <symbol id="a-low-wing-retractable" viewBox="1 1 46 46" fill="currentColor"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L35 18.6H24.5L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M34.2 19.3L37.6 21H33.8V19.3ZM25.2 19.3H32.8V21H25.2Z" style="fill:var(--avatar-bg,#fff)"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="20" y="26.4" width="14.5" height="2.0" rx="0.8"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/></symbol>
36
+ <symbol id="a-low-wing-tailwheel" viewBox="1 1 46 46" fill="currentColor"><g transform="rotate(-8 24 28)"><path d="M43.5 24.2C43.5 22 41.5 21 39 21H31L28 18.4C26.5 17.6 23.5 17.6 22 18.6L19.5 21H9L5 21.4V24L24 27H38.5C41.5 27 43.5 26.4 43.5 24.2Z"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="20" y="26.4" width="14.5" height="2.0" rx="0.8"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="32.5" y1="27" x2="34" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="34" cy="33.6" r="2.3"/><line x1="7.5" y1="23.8" x2="7" y2="25.6" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><circle cx="7" cy="26.2" r="1.1"/></g></symbol>
37
+ <symbol id="a-biplane" viewBox="1 1 46 46" fill="currentColor"><g transform="rotate(-8 24 28)"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L31 20.4C30 18.4 26.5 18.4 25.5 20.4L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="20" y="13.6" width="14" height="2.0" rx="0.8"/><line x1="25" y1="15.4" x2="26.5" y2="19.5" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><line x1="29.5" y1="15.4" x2="30" y2="19" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="21" y="26.4" width="13" height="1.8" rx="0.8"/><line x1="22" y1="15.4" x2="22" y2="26.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><line x1="32.5" y1="15.4" x2="32.5" y2="26.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="32.5" y1="27" x2="34" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="34" cy="33.6" r="2.3"/><line x1="7.5" y1="23.8" x2="7" y2="25.6" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><circle cx="7" cy="26.2" r="1.1"/></g></symbol>
38
+ <symbol id="a-ag-plane" viewBox="1 1 46 46" fill="currentColor"><g transform="rotate(-8 24 28)"><path d="M43.5 24.4C43.5 22.2 42 21.4 40 21.2L37 18.8H31.5L28.5 15.6C27.5 14.8 24.5 14.8 23.5 15.8L21.5 19.6L9 21.4H5V24.2L24 27.6H38.5C41.5 27.6 43.5 26.6 43.5 24.4Z"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="20" y="26.4" width="14.5" height="2.0" rx="0.8"/><rect x="19.5" y="29.4" width="15.5" height="0.8" rx="0.4"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><line x1="32.5" y1="27" x2="34" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="34" cy="33.6" r="2.3"/><line x1="7.5" y1="23.8" x2="7" y2="25.6" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><circle cx="7" cy="26.2" r="1.1"/></g></symbol>
39
+ <symbol id="a-floatplane" viewBox="1 1 46 46" fill="currentColor"><path d="M43.5 24.2C43.5 22 41.5 20.8 39 20.6L35 18.6H24.5L9 21.4H5V24.2L24 27.2H38.5C41.5 27.2 43.5 26.4 43.5 24.2Z"/><path d="M34.2 19.3L37.6 21H33.8V19.3ZM25.2 19.3H32.8V21H25.2Z" style="fill:var(--avatar-bg,#fff)"/><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="21.5" y="16.6" width="13.0" height="2.0" rx="0.8"/><line x1="31" y1="18.4" x2="27" y2="26.4" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="44.4" y="15.5" width="1.2" height="17.4" rx="0.6"/><circle cx="44.2" cy="24.2" r="1.4"/><path d="M13 32.4H38C41 32.4 42.6 31.4 43.4 29.8L44 30.6C43.4 33 41.4 34.8 38 34.8H15L13 33.8Z"/><line x1="22" y1="27" x2="21" y2="32.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><line x1="33.5" y1="27" x2="34.5" y2="32.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></symbol>
40
+ <symbol id="a-single-turboprop" viewBox="1 1 46 46" fill="currentColor"><path d="M45 24.4C45 22.6 43.6 21.6 41.6 21.4L33 20.8L30 18.4H21L7 21.2H3.6V24L22 27.4H41.6C43.8 27.4 45 26.4 45 24.4Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M29.2 19.1L31 20.6H28.4V19.1Z"/><rect x="21.6" y="19.1" width="5.8" height="1.5" rx="0.3"/></g><rect x="35.5" y="19.6" width="2.5" height="2.0" rx="0.5"/><path d="M3.6 21.4L2.6 11.6H6.6L13 21Z"/><rect x="1.2" y="21" width="10.4" height="1.4" rx="0.7"/><rect x="18" y="16.4" width="14" height="2.0" rx="0.8"/><line x1="28" y1="18.2" x2="24.6" y2="26.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><path d="M17 27.4H34C35.4 27.4 36 28 36 28.8C36 29.6 35.4 30.2 34 30.2H19Z"/><rect x="45.6" y="15.4" width="1.2" height="18.0" rx="0.6"/><line x1="40" y1="27.4" x2="40" y2="31.6" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="40" cy="33.2" r="1.8"/><line x1="26" y1="30" x2="27" y2="32" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="27" cy="33.4" r="2.1"/></symbol>
41
+ <symbol id="a-twin" viewBox="1 1 46 46" fill="currentColor"><path d="M45 24.6C45 22.8 43 21.6 40 21.2L35.4 18.6H24.5L9 21.4H5V24.2L24 27.2H40C43 27.2 45 26.4 45 24.6Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M34.6 19.3L37.8 21H34.2V19.3Z"/><rect x="25.2" y="19.3" width="7.8" height="1.7" rx="0.3"/></g><path d="M5 21.6L4 12.6H7.6L14 21Z"/><rect x="2.5" y="21.2" width="10.0" height="1.4" rx="0.7"/><rect x="19" y="26.4" width="14" height="2.0" rx="0.8"/><path d="M20 24.2H34.6C37.2 24.2 38.4 25.8 38.4 27.6C38.4 29.4 37.2 31 34.6 31H20Z"/><ellipse cx="38.4" cy="27.6" rx="0.8" ry="7"/></symbol>
42
+ <symbol id="a-business-jet" viewBox="1 1 46 46" fill="currentColor"><path d="M46.6 24.8C45 22.6 42.6 21.2 39 20.8L36 19.2H20L7 21.2H4V24.4L20 27H38C42.6 27 45.6 26.2 46.6 24.8Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M37.8 20.2L39.8 21.2H37.2Z"/><circle cx="32" cy="21.2" r="0.8"/><circle cx="29" cy="21.2" r="0.8"/><circle cx="26" cy="21.2" r="0.8"/><circle cx="23" cy="21.4" r="0.8"/></g><path d="M4.4 21.4L5.4 10.6H8.4L13.6 21Z"/><rect x="2.6" y="9.8" width="8.8" height="1.4" rx="0.7"/><path d="M9 17.4H17C19 17.4 20 18.4 20 19.4C20 20.4 19 21.4 17 21.4H9Z"/><path d="M18 26.4L30 26.2L34 27.8L22 28.4Z"/></symbol>
43
+ <symbol id="a-airliner" viewBox="1 1 46 46" fill="currentColor"><path d="M47 24.6C46 22.4 44 21.2 41 21.2H8L3.4 18.6H1V24.4L6 27.4H41C44 27.4 46.4 26.2 47 24.6Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M42 22.2L44.4 23.2H41.6Z"/><circle cx="10" cy="23" r="0.55"/><circle cx="13" cy="23" r="0.55"/><circle cx="16" cy="23" r="0.55"/><circle cx="19" cy="23" r="0.55"/><circle cx="22" cy="23" r="0.55"/><circle cx="25" cy="23" r="0.55"/><circle cx="28" cy="23" r="0.55"/><circle cx="31" cy="23" r="0.55"/><circle cx="34" cy="23" r="0.55"/><circle cx="37" cy="23" r="0.55"/></g><path d="M1.4 19L1 8.8H4.4L11.2 21.2Z"/><rect x="0" y="20.4" width="8.6" height="1.4" rx="0.7"/><path d="M18 26.6L30 26.4L34 28.4L22 29Z"/><path d="M24 28.2H31C32.6 28.2 33.4 29 33.4 30C33.4 31 32.6 31.8 31 31.8H24Z"/></symbol>
44
+ <symbol id="a-helicopter" viewBox="1 1 46 46" fill="currentColor"><path d="M39 26.4C39 21.4 36 18.4 31 18.4H27L24.6 16.4H18.6L16 19L4.6 21.6V23.4L19 25.4C19.6 28.4 21.6 30 24.6 30H35.4C37.6 30 39 28.6 39 26.4Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M31.4 19.8C35 19.8 37.4 22.4 37.6 25.8H32Z"/><rect x="25.4" y="20.6" width="4.0" height="4.4" rx="0.6"/></g><rect x="22.6" y="12.6" width="1.8" height="4" rx="0.5"/><rect x="4" y="11.4" width="40" height="1.4" rx="0.7"/><path d="M4.8 22L3 13.6H5.6L8.8 21.6Z"/><rect x="9" y="21.4" width="6" height="1.2" rx="0.5"/><circle cx="4.4" cy="17.4" r="2.6" fill="none" stroke="currentColor" stroke-width="0.8"/><line x1="25" y1="30" x2="24.4" y2="33" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><line x1="35" y1="30" x2="35.6" y2="33" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><rect x="20" y="32.8" width="20" height="1.2" rx="0.6"/></symbol>
45
+ <symbol id="a-heavy-helicopter" viewBox="1 1 46 46" fill="currentColor"><path d="M43 26C43 22 40.4 19.6 36.4 19.4L33 17.4H17.4L15 19.6L3.6 20.4V22.8L15 24.4V28.4C15 29.4 15.8 30 17 30H39.4C41.6 30 43 28.6 43 26Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M37 20.8C39.6 21 41.2 22.6 41.6 25H37.4Z"/><rect x="30" y="21.4" width="4.4" height="3.0" rx="0.5"/><rect x="23.4" y="21.4" width="4.4" height="3.0" rx="0.5"/><rect x="17.6" y="21.4" width="4.0" height="3.0" rx="0.5"/></g><path d="M14.6 17.4H34.6L33 15.6H17Z"/><rect x="24.4" y="11.4" width="2.0" height="4.4" rx="0.5"/><rect x="2.4" y="10.4" width="44.6" height="1.4" rx="0.7"/><path d="M4 21L2.4 12.6H5.4L9 20.6Z"/><rect x="5" y="19.4" width="7" height="1.2" rx="0.5"/><line x1="37" y1="30" x2="37" y2="32.6" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="37" cy="34" r="1.8"/><line x1="21" y1="30" x2="21" y2="32.6" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><circle cx="21" cy="34" r="1.9"/><circle cx="4.6" cy="23.6" r="1"/></symbol>
46
+ <symbol id="a-multirotor-drone" viewBox="1 1 46 46" fill="currentColor"><g transform="translate(24 24) scale(.84) translate(-24 -24)"><line x1="11" y1="11" x2="37" y2="37" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/><line x1="37" y1="11" x2="11" y2="37" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/><circle cx="11" cy="11" r="7.4" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="37" cy="11" r="7.4" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="11" cy="37" r="7.4" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="37" cy="37" r="7.4" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="11" cy="11" r="1.6"/><circle cx="37" cy="11" r="1.6"/><circle cx="11" cy="37" r="1.6"/><circle cx="37" cy="37" r="1.6"/><rect x="18.4" y="17" width="11.2" height="14" rx="3"/><g style="fill:var(--avatar-bg,#fff)"><rect x="20.6" y="19.6" width="6.8" height="4.4" rx="1.4"/></g><rect x="9" y="40.6" width="30" height="1.4" rx="0.7"/></g></symbol>
47
+ <symbol id="a-fixed-wing-drone" viewBox="1 1 46 46" fill="currentColor"><g transform="translate(24 24) scale(.84) translate(-24 -24)"><path d="M24 6C25.6 6 26.6 7.6 26.6 10V36H21.4V10C21.4 7.6 22.4 6 24 6Z"/><rect x="2" y="17" width="44" height="4.4" rx="1.4"/><rect x="11" y="9" width="2" height="26" rx="1"/><rect x="35" y="9" width="2" height="26" rx="1"/><circle cx="12" cy="9" r="4.4" fill="none" stroke="currentColor" stroke-width="1.1"/><circle cx="12" cy="35" r="4.4" fill="none" stroke="currentColor" stroke-width="1.1"/><circle cx="36" cy="9" r="4.4" fill="none" stroke="currentColor" stroke-width="1.1"/><circle cx="36" cy="35" r="4.4" fill="none" stroke="currentColor" stroke-width="1.1"/><rect x="17" y="36" width="14" height="2" rx="0.8"/><ellipse cx="24" cy="40" rx="5.4" ry="1"/></g></symbol>
48
+ <symbol id="a-glider" viewBox="1 1 46 46" fill="currentColor"><path d="M46 24.4C46 23 44 22.2 41 21.8L36 20.2C34.4 19.4 31 19.4 29.6 20.4L28 21.4L8 22.6H4.4V24.6L8 25.2L28 27H40C44 27 46 25.8 46 24.4Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M35.8 21.4C34.6 20.4 31.6 20.4 30.4 21.4Z"/></g><path d="M4.6 22.8L5.6 12H8.4L12 22.6Z"/><rect x="3.4" y="11.2" width="8.2" height="1.2" rx="0.6"/><rect x="22" y="22.2" width="11" height="1.4" rx="0.6"/><circle cx="30.4" cy="28" r="1.6"/></symbol>
49
+ <symbol id="a-balloon" viewBox="1 1 46 46" fill="currentColor"><path d="M24 2.6C32.6 2.6 38.6 8.4 38.6 16.4C38.6 23.6 31.6 29 27.2 32.6H20.8C16.4 29 9.4 23.6 9.4 16.4C9.4 8.4 15.4 2.6 24 2.6Z"/><g style="fill:var(--avatar-bg,#fff)"><path d="M19.6 4.4C17.4 9 17 20 21.4 31.6H22.6C19.4 20 19.8 9 21.6 3.8Z"/><path d="M28.4 4.4C30.6 9 31 20 26.6 31.6H25.4C28.6 20 28.2 9 26.4 3.8Z"/></g><line x1="21" y1="32.6" x2="21.6" y2="38.6" stroke="currentColor" stroke-width="0.7" stroke-linecap="round"/><line x1="27" y1="32.6" x2="26.4" y2="38.6" stroke="currentColor" stroke-width="0.7" stroke-linecap="round"/><rect x="20.4" y="38.2" width="7.2" height="6.0" rx="0.8"/></symbol>
50
+ <symbol id="a-trike" viewBox="1 1 46 46" fill="currentColor"><path d="M2 17.6L42 8.4C43.6 8.2 44.6 9 44.2 10.2L43.6 11.4L4 19.8Z"/><line x1="26" y1="13.6" x2="24" y2="25" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><line x1="26" y1="13.6" x2="34.6" y2="22.6" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><line x1="26" y1="13.6" x2="30.6" y2="23.4" stroke="currentColor" stroke-width="0.9" stroke-linecap="round"/><line x1="30.6" y1="23.4" x2="34.6" y2="22.6" stroke="currentColor" stroke-width="1.1" stroke-linecap="round"/><path d="M38 27.4C38 25.4 36 24 33 24H24L20 25V29.4H34C36.6 29.4 38 28.8 38 27.4Z"/><ellipse cx="18.4" cy="27.2" rx="1" ry="6.2"/><line x1="34" y1="29.4" x2="35" y2="32.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><circle cx="35" cy="34.2" r="1.8"/><line x1="23" y1="29.4" x2="22" y2="32.6" stroke="currentColor" stroke-width="1" stroke-linecap="round"/><circle cx="22" cy="34.2" r="1.9"/></symbol>
51
+ <symbol id="a-powered-parachute" viewBox="1 1 46 46" fill="currentColor"><path d="M4 13C10 4 38 4 44 13L41.6 14.6C35.6 8 12.4 8 6.4 14.6Z"/><line x1="7" y1="14" x2="22" y2="30" stroke="currentColor" stroke-width="0.6" stroke-linecap="round"/><line x1="17" y1="8.4" x2="23" y2="30" stroke="currentColor" stroke-width="0.6" stroke-linecap="round"/><line x1="31" y1="8.4" x2="25" y2="30" stroke="currentColor" stroke-width="0.6" stroke-linecap="round"/><line x1="41" y1="14" x2="26" y2="30" stroke="currentColor" stroke-width="0.6" stroke-linecap="round"/><path d="M34 32C34 30.2 32 29 29 29H20L16 30V34H30C32.6 34 34 33.4 34 32Z"/><circle cx="14.4" cy="31" r="5.2" fill="none" stroke="currentColor" stroke-width="1"/><circle cx="29" cy="36.4" r="1.7"/><circle cx="20" cy="36.4" r="1.8"/></symbol>
52
+ </svg>
@@ -0,0 +1,100 @@
1
+ // The plane of render "easy/flyer": across the sky in 14 seconds on a gentle wave, nose along it, its trail behind it.
2
+ // On a holiday, by the visitor's own calendar, it flies in the day's colors with a line of smoke per color: every day
3
+ // is in this file, so whatever cached it, it's right at midnight. No plane where motion is off. ES5, for old phones.
4
+ (function () {
5
+ if (!document.querySelectorAll || !document.createElementNS || !window.requestAnimationFrame) { return; }
6
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { return; }
7
+
8
+ var SKY = { trail: ['#5aa9e6'], opacity: 0.45 }, HANUKKAH = { plane: '#2f6fd0', trail: ['#2f6fd0', '#cfd8e3'] };
9
+ var GOLD = { plane: '#d4a514', trail: ['#d4a514', '#a8b0b8'] };
10
+ var USA = { plane: '#2f5fc4', trail: ['#d63a3a', '#ffffff', '#2f5fc4'] };
11
+ // Hanukkah's first day (25 Kislev) as a day of December, 2025 to 2045 (Nov 28 is -2); its days yield to the others.
12
+ var KISLEV_25 = [15, 5, 25, 13, 2, 21, 10, -2, 17, 7, 26, 14, 3, 22, 12, 0, 18, 8, 27, 15, 5];
13
+
14
+ // The holidays of a year, by "month-day": the plane's color, then its trail's, top to bottom. National Aviation Day
15
+ // is in pilot's-wings gold and blue, Wright Brothers Day in the Wright Flyer's muslin and spruce.
16
+ function holidays(year) {
17
+ var easter = easterOf(year), days = {};
18
+ for (var k = 0; k < 16; k++) { // last year's eight days of Hanukkah, reaching into January, then this year's
19
+ var y = year - 1 + (k >> 3), day = new Date(y, 11, KISLEV_25[y - 2025] + k % 8);
20
+ if (day.getFullYear() === year) { days[(day.getMonth() + 1) + '-' + day.getDate()] = HANUKKAH; }
21
+ }
22
+ days['1-1'] = days['12-31'] = GOLD;
23
+ days['2-14'] = { plane: '#e0457b', trail: ['#d62f4b', '#f07aa0'] };
24
+ days['3-17'] = { plane: '#1f9d55', trail: ['#1f9d55', '#7cc576'] };
25
+ days[easter] = { plane: '#a77bd6', trail: ['#f2c94c', '#a77bd6', '#6fcf97'] };
26
+ days['5-' + weekday(year, 5, 1, -1)] = days['7-4'] = days['11-11'] = USA;
27
+ days['8-19'] = { plane: '#1a70d0', trail: ['#e0b43a', '#5aa9e6'] };
28
+ days['10-31'] = { plane: '#f28c28', trail: ['#f28c28', '#7b3fa0'] };
29
+ days['11-' + weekday(year, 11, 4, 4)] = { plane: '#c0662b', trail: ['#e0a526', '#c0662b', '#8a4b2a'] };
30
+ days['12-17'] = { plane: '#8a6a43', trail: ['#d9c9a3', '#8a6a43'] };
31
+ days['12-25'] = { plane: '#d42f2f', trail: ['#1f8a4c', '#d42f2f'] };
32
+ return days;
33
+ }
34
+
35
+ // The day of the month of its nth weekday (0 Sunday), the last for n -1: Memorial Day, Thanksgiving.
36
+ function weekday(year, month, day, n) {
37
+ if (n < 0) { var last = new Date(year, month, 0); return last.getDate() - (last.getDay() - day + 7) % 7; }
38
+ return 1 + (day - new Date(year, month - 1, 1).getDay() + 7) % 7 + (n - 1) * 7;
39
+ }
40
+
41
+ // Easter Sunday's "month-day": the anonymous Gregorian algorithm.
42
+ function easterOf(y) {
43
+ var a = y % 19, b = Math.floor(y / 100), c = y % 100, f = Math.floor((b + 8) / 25);
44
+ var h = (19 * a + b - Math.floor(b / 4) - Math.floor((b - f + 1) / 3) + 15) % 30;
45
+ var l = (32 + 2 * (b % 4) + 2 * Math.floor(c / 4) - h - c % 4) % 7, m = Math.floor((a + 11 * h + 22 * l) / 451);
46
+ return Math.floor((h + l - 7 * m + 114) / 31) + '-' + ((h + l - 7 * m + 114) % 31 + 1);
47
+ }
48
+
49
+ // The sky's colors: its data-date's ("2026-12-25"), to see a holiday on any day, or today's.
50
+ function colorsOf(sky) {
51
+ var date = /^(\d{4})-(\d\d)-(\d\d)$/.exec(sky.getAttribute('data-date') || '');
52
+ var day = date ? new Date(+date[1], date[2] - 1, +date[3]) : new Date();
53
+ return holidays(day.getFullYear())[(day.getMonth() + 1) + '-' + day.getDate()] || SKY;
54
+ }
55
+
56
+ function svg(name, attributes, parent) {
57
+ var node = document.createElementNS('http://www.w3.org/2000/svg', name);
58
+ for (var key in attributes) { node.setAttribute(key, attributes[key]); }
59
+ return parent.appendChild(node);
60
+ }
61
+
62
+ function fly(sky, n) {
63
+ var flyer = sky.querySelector('.js-flyer'), trail = sky.querySelector('.js-trail'), colors = colorsOf(sky);
64
+ var defs = svg('defs', {}, trail), lines = [], fades = [];
65
+ if (colors.plane) { flyer.style.color = colors.plane; }
66
+ // One line per color, 3px apart, each fading in from the sky's left edge to the tail.
67
+ for (var i = 0; i < colors.trail.length; i++) {
68
+ var id = 'flyer-' + n + '-' + i, color = colors.trail[i];
69
+ var fade = svg('linearGradient', { id: id, gradientUnits: 'userSpaceOnUse', x1: 0, y1: 0, x2: 100, y2: 0 }, defs);
70
+ svg('stop', { offset: 0, 'stop-color': color, 'stop-opacity': 0 }, fade);
71
+ svg('stop', { offset: 1, 'stop-color': color, 'stop-opacity': colors.opacity || 0.7 }, fade);
72
+ fades.push(fade);
73
+ lines.push(svg('polyline', { fill: 'none', stroke: 'url(#' + id + ')', 'stroke-width': 2, 'stroke-linecap': 'round' }, trail));
74
+ }
75
+ var span = 14000, size = 34, began = null;
76
+ function wave(x, width) { return 14 + 9 * Math.sin((x / width) * Math.PI * 3); }
77
+ function frame(now) {
78
+ if (began === null) { began = now; }
79
+ var width = sky.offsetWidth, t = ((now - began) % span) / span;
80
+ var x = -size - 40 + (width + size + 80) * t, y = wave(x + size / 2, width);
81
+ var angle = Math.atan(wave(x + size / 2 + 1, width) - y) * 180 / Math.PI;
82
+ var move = 'translate(' + x.toFixed(1) + 'px,' + (y - size / 2).toFixed(1) + 'px) rotate(' + angle.toFixed(1) + 'deg)';
83
+ flyer.style.webkitTransform = move; flyer.style.transform = move;
84
+ // The tail is the plane's left edge, at its middle, turned with it.
85
+ var tailX = x + size / 2 - (size / 2 - 3) * Math.cos(angle * Math.PI / 180);
86
+ for (var i = 0; i < lines.length; i++) {
87
+ var shift = (i - (lines.length - 1) / 2) * 3, points = [];
88
+ for (var px = 0; px < tailX; px += 6) { points.push(px + ',' + (wave(px, width) + shift).toFixed(1)); }
89
+ if (tailX > 0) { points.push(tailX.toFixed(1) + ',' + (wave(tailX, width) + shift).toFixed(1)); }
90
+ lines[i].setAttribute('points', points.join(' '));
91
+ fades[i].setAttribute('x2', Math.max(tailX, 1).toFixed(1));
92
+ }
93
+ if (sky.className.indexOf('is-gone') < 0) { window.requestAnimationFrame(frame); } // freed (easy/konami)
94
+ }
95
+ window.requestAnimationFrame(frame);
96
+ }
97
+
98
+ var skies = document.querySelectorAll('.js-sky');
99
+ for (var i = 0; i < skies.length; i++) { fly(skies[i], i); }
100
+ })();
@@ -0,0 +1,68 @@
1
+ // The easter egg of render "easy/flyer": the Konami code sets the plane free, ↑ ↑ ↓ ↓ ← → ← → B A on a keyboard,
2
+ // or on a phone swipes up, up, down, down, left, right, left, right and two taps. It leaves its wave, wanders the
3
+ // window at random until it flies out of it, and never comes back in this browser (localStorage, per site).
4
+ // Included with easy/flyer; where motion is turned off there is no plane, and nothing to free.
5
+ // ES5 alone: every easy app has to work on old iPhones, Androids and Windows browsers.
6
+ (function () {
7
+ if (!document.querySelectorAll || !document.addEventListener || !window.requestAnimationFrame) { return; }
8
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { return; }
9
+ var GONE = 'easy-plane-gone', CODES = ['UUDDLRLRBA', 'UUDDLRLRTT'];
10
+ var KEYS = { 38: 'U', 40: 'D', 37: 'L', 39: 'R', 66: 'B', 65: 'A' };
11
+ var skies = document.querySelectorAll('.js-sky'), typed = '', start = null, freed = false;
12
+
13
+ // A sky gone keeps no plane, and its trail fades; easy/flyer stops drawing it.
14
+ function gone() { for (var i = 0; i < skies.length; i++) { skies[i].className += ' is-gone'; } }
15
+ try { if (window.localStorage.getItem(GONE)) { gone(); return; } } catch (err) { /* no storage: it comes back */ }
16
+
17
+ // The plane, out of its sky and over the window, where it was and heading as it was: at 160px a second, it turns
18
+ // one way then the other, never sharper than a loop in three seconds, until it is past an edge.
19
+ function fly(flyer) {
20
+ var box = flyer.getBoundingClientRect(), plane = flyer.cloneNode(true), size = 34;
21
+ var rotation = /rotate\((-?[\d.]+)deg\)/.exec(flyer.style.transform || '');
22
+ var x = box.left + box.width / 2 - size / 2, y = box.top + box.height / 2 - size / 2;
23
+ var heading = rotation ? rotation[1] * Math.PI / 180 : 0, turn = 0, last = null;
24
+ if (!box.width) { return; }
25
+ function place() {
26
+ var move = 'translate(' + x.toFixed(1) + 'px,' + y.toFixed(1) + 'px) rotate(' + (heading * 180 / Math.PI).toFixed(1) + 'deg)';
27
+ plane.style.webkitTransform = move; plane.style.transform = move;
28
+ }
29
+ plane.className = 'flyer is-free';
30
+ place();
31
+ document.body.appendChild(plane);
32
+ function frame(now) {
33
+ var dt = last === null ? 16 : Math.min(now - last, 50);
34
+ last = now;
35
+ turn = Math.max(-0.002, Math.min(0.002, turn + (Math.random() - 0.5) * 0.00002 * dt));
36
+ heading += turn * dt;
37
+ x += Math.cos(heading) * 0.16 * dt; y += Math.sin(heading) * 0.16 * dt;
38
+ place();
39
+ if (x < -size || y < -size || x > window.innerWidth || y > window.innerHeight) { return document.body.removeChild(plane); }
40
+ window.requestAnimationFrame(frame);
41
+ }
42
+ window.requestAnimationFrame(frame);
43
+ }
44
+
45
+ function free() {
46
+ try { window.localStorage.setItem(GONE, '1'); } catch (err) { /* no storage: it comes back */ }
47
+ for (var i = 0; i < skies.length; i++) { fly(skies[i].querySelector('.js-flyer')); }
48
+ gone();
49
+ }
50
+
51
+ function push(symbol) {
52
+ typed = (typed + symbol).slice(-10);
53
+ if (!freed && (typed === CODES[0] || typed === CODES[1])) { freed = true; free(); }
54
+ }
55
+
56
+ document.addEventListener('keydown', function (event) { push(KEYS[event.keyCode] || '-'); });
57
+ // A swipe is its longer way, 30px at least; a touch that hardly moved is a tap.
58
+ document.addEventListener('touchstart', function (event) {
59
+ start = event.touches.length === 1 ? { x: event.touches[0].clientX, y: event.touches[0].clientY } : null;
60
+ }, { passive: true });
61
+ document.addEventListener('touchend', function (event) {
62
+ if (!start) { return; }
63
+ var dx = event.changedTouches[0].clientX - start.x, dy = event.changedTouches[0].clientY - start.y;
64
+ var far = Math.max(Math.abs(dx), Math.abs(dy));
65
+ if (far < 10) { push('T'); } else if (far < 30) { push('-'); } else if (Math.abs(dx) > Math.abs(dy)) { push(dx > 0 ? 'R' : 'L'); } else { push(dy > 0 ? 'D' : 'U'); }
66
+ start = null;
67
+ }, { passive: true });
68
+ })();
@@ -0,0 +1,48 @@
1
+ // Every dialog of render "easy/modal" lets its reader out: its ×, Esc or a click around it closes it where it is, without loading a page,
2
+ // and the address becomes the × link's (the page without what opened it), or loses the #id that opened it. A link to
3
+ // that #id opens it again. Without the script, the × is a plain link.
4
+ // ES5 alone: every easy app has to work on old iPhones, Androids and Windows browsers.
5
+ (function () {
6
+ if (!document.querySelector || !document.addEventListener) { return; }
7
+
8
+ function close(modal) {
9
+ var link = modal.querySelector('.modal-x'), href = link && link.getAttribute('href');
10
+ modal.className = modal.className.replace(/\s*\bis-(open|closed)\b/g, '') + ' is-closed';
11
+ if (!window.history || !history.replaceState) { return; }
12
+ if (href && href.charAt(0) !== '#') { history.replaceState(null, '', href + location.hash); }
13
+ else if (location.hash === '#' + modal.id) { history.replaceState(null, '', location.pathname + location.search); }
14
+ }
15
+
16
+ // The dialog showing: drawn open, or the one the address's #id names.
17
+ function shown() {
18
+ var target = location.hash.length > 1 && document.getElementById(location.hash.slice(1));
19
+ var open = document.querySelector('.modal.is-open');
20
+ if (open) { return open; }
21
+ return target && /(^|\s)modal(\s|$)/.test(target.className) && !/\bis-closed\b/.test(target.className) ? target : null;
22
+ }
23
+
24
+ // The node or its nearest ancestor with the class: old browsers have no closest().
25
+ function up(node, name) {
26
+ var pattern = new RegExp('(^|\\s)' + name + '(\\s|$)');
27
+ while (node && node.nodeType === 1 && !pattern.test(node.className)) { node = node.parentNode; }
28
+ return node && node.nodeType === 1 ? node : null;
29
+ }
30
+
31
+ // The ×, or the dimmed page around the box (the dialog itself, not what it holds).
32
+ document.addEventListener('click', function (event) {
33
+ var x = up(event.target, 'modal-x');
34
+ var modal = x ? up(x, 'modal') : /(^|\s)modal(\s|$)/.test(event.target.className) && event.target;
35
+ if (!modal) { return; }
36
+ event.preventDefault();
37
+ close(modal);
38
+ });
39
+ document.addEventListener('keydown', function (event) {
40
+ if (event.key !== 'Escape' && event.key !== 'Esc' && event.keyCode !== 27) { return; }
41
+ var modal = shown();
42
+ if (modal) { event.preventDefault(); close(modal); }
43
+ });
44
+ window.addEventListener('hashchange', function () {
45
+ var target = location.hash.length > 1 && document.getElementById(location.hash.slice(1));
46
+ if (target) { target.className = target.className.replace(/\s*\bis-closed\b/g, ''); }
47
+ });
48
+ })();
@@ -0,0 +1,43 @@
1
+ // The 6-digit code of render "easy/pin_code": its six slots show what its one real field holds, the next one to fill
2
+ // lit, and six digits submit the form without a click, typed, pasted or offered by the phone from the email. A field
3
+ // the page empties redraws on an input event; a code refused is the page's to mark, is-invalid on the .pin-code,
4
+ // which the next digit clears. A .pin-code already drawn is left alone, so including the script twice is harmless.
5
+ // ES5 alone: every easy app has to work on old iPhones, Androids and Windows browsers.
6
+ (function () {
7
+ if (!document.querySelectorAll || !document.addEventListener) { return; }
8
+
9
+ // The form's own submit, so its handlers run first: requestSubmit, or an old browser's submit event.
10
+ function submit(form) {
11
+ if (form.requestSubmit) { form.requestSubmit(); return; }
12
+ var event = document.createEvent('Event');
13
+ event.initEvent('submit', true, true);
14
+ if (form.dispatchEvent(event)) { form.submit(); }
15
+ }
16
+
17
+ function drawn(pin) {
18
+ var input = pin.querySelector('.pin-code-input'), slots = pin.querySelectorAll('.pin-code-slots span');
19
+ function draw() {
20
+ var value = input.value.replace(/\D/g, '').slice(0, 6), focused = document.activeElement === input;
21
+ if (value !== input.value) { input.value = value; }
22
+ for (var i = 0; i < slots.length; i++) {
23
+ slots[i].textContent = value.charAt(i);
24
+ slots[i].className = focused && i === Math.min(value.length, 5) ? 'is-active' : '';
25
+ }
26
+ return value;
27
+ }
28
+ input.addEventListener('input', function () {
29
+ pin.className = pin.className.replace(/\s*\bis-invalid\b/g, '');
30
+ input.removeAttribute('aria-invalid');
31
+ if (draw().length === 6 && input.form) { submit(input.form); }
32
+ });
33
+ input.addEventListener('focus', draw);
34
+ input.addEventListener('blur', draw);
35
+ pin.className += ' is-drawn';
36
+ draw();
37
+ }
38
+
39
+ var pins = document.querySelectorAll('.pin-code');
40
+ for (var i = 0; i < pins.length; i++) {
41
+ if (!/\bis-drawn\b/.test(pins[i].className)) { drawn(pins[i]); }
42
+ }
43
+ })();
@@ -0,0 +1,44 @@
1
+ // The sort button of easy/report_head: flips the list named by data-sort-list by each row's data-sort, remembered in
2
+ // this browser. ES5 alone. A page that redraws its list calls window.easyReportSort() after. Without this script the
3
+ // list stays as the server sent it, and the button hidden.
4
+ (function () {
5
+ function saved(head) {
6
+ try { return localStorage.getItem('report-sort:' + head.getAttribute('data-sort-list')); } catch (blocked) { return null; }
7
+ }
8
+
9
+ function save(head, order) {
10
+ try { localStorage.setItem('report-sort:' + head.getAttribute('data-sort-list'), order); } catch (blocked) { return; }
11
+ }
12
+
13
+ function order(head) { return head.easyOrder || saved(head) || head.getAttribute('data-sort-default'); }
14
+
15
+ function compared(a, b) {
16
+ return a.getAttribute('data-sort').localeCompare(b.getAttribute('data-sort'), undefined, { numeric: true });
17
+ }
18
+
19
+ function apply(head) {
20
+ var list = document.querySelector(head.getAttribute('data-sort-list')), item = head.querySelector('.sort-button-item');
21
+ if (!list || !item) { return; }
22
+ var way = order(head), newest = way === 'newest';
23
+ Array.prototype.slice.call(list.children).filter(function (row) { return row.hasAttribute('data-sort'); })
24
+ .sort(function (a, b) { return newest ? compared(b, a) : compared(a, b); })
25
+ .forEach(function (row) { list.appendChild(row); });
26
+ item.hidden = false;
27
+ item.querySelector('.js-sort-label').textContent = newest ? 'Newest first' : 'Oldest first';
28
+ var icon = item.querySelector('use'), ref = icon.getAttribute('href').split('#')[0] + '#i-' + way;
29
+ icon.setAttribute('href', ref);
30
+ icon.setAttribute('xlink:href', ref);
31
+ }
32
+
33
+ window.easyReportSort = function () { Array.prototype.forEach.call(document.querySelectorAll('[data-sort-list]'), apply); };
34
+
35
+ document.addEventListener('click', function (event) {
36
+ var button = event.target.closest && event.target.closest('.js-sort'), head = button && button.closest('[data-sort-list]');
37
+ if (!head) { return; }
38
+ head.easyOrder = order(head) === 'newest' ? 'oldest' : 'newest';
39
+ save(head, head.easyOrder);
40
+ apply(head);
41
+ });
42
+
43
+ window.easyReportSort();
44
+ }());