wordmark 0.22.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +47 -15
- data/app/assets/fonts/easy/outfit-latin-ext.woff2 +0 -0
- data/app/assets/fonts/easy/outfit-latin.woff2 +0 -0
- data/app/assets/images/easy/sprite.svg +52 -0
- data/app/assets/javascripts/easy/flyer.js +100 -0
- data/app/assets/javascripts/easy/konami.js +68 -0
- data/app/assets/javascripts/easy/modal.js +48 -0
- data/app/assets/javascripts/easy/pin_code.js +43 -0
- data/app/assets/javascripts/easy/report_sort.js +44 -0
- data/app/assets/javascripts/easy/rotating_placeholder.js +35 -0
- data/app/assets/stylesheets/easy/base.css +35 -0
- data/app/assets/stylesheets/easy/components.css +388 -0
- data/app/assets/stylesheets/easy/layout.css +44 -0
- data/app/assets/stylesheets/easy/tokens.css +9 -0
- data/app/controllers/wordmark/errors_controller.rb +41 -0
- data/app/helpers/wordmark/easy_helper.rb +28 -0
- data/app/models/aircraft_avatar.rb +59 -0
- data/app/views/easy/_flyer.html.erb +10 -0
- data/app/views/easy/_modal.html.erb +14 -0
- data/app/views/easy/_pin_code.html.erb +9 -0
- data/app/views/easy/_report_head.html.erb +19 -0
- data/app/views/easy/mailer/_code.html.erb +67 -0
- data/app/views/easy/mailer/_code.text.erb +16 -0
- data/app/views/layouts/easy/site.html.erb +50 -0
- data/app/views/layouts/wordmark/errors.html.erb +26 -0
- data/app/views/wordmark/errors/show.html.erb +15 -0
- data/config/aircraft_avatars.yml +26 -0
- data/lib/wordmark/engine.rb +15 -12
- data/lib/wordmark/version.rb +1 -1
- data/lib/wordmark.rb +10 -5
- data/public/_headers +4 -4
- metadata +28 -3
- data/lib/wordmark/down.rb +0 -19
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Which of the 20 aircraft avatars (sprite a-*, easy_aircraft_avatar) draws an aircraft before
|
|
2
|
+
# anyone has a picture of it: from what the registry says it is, and for a single-engine airplane
|
|
3
|
+
# from its make and model too (config/aircraft_avatars.yml), so a Cub, a 172 and a Bonanza differ.
|
|
4
|
+
module AircraftAvatar
|
|
5
|
+
# The single-engine airplane families by make and model, in the order they are tried.
|
|
6
|
+
FAMILIES = YAML.load_file(File.expand_path('../../config/aircraft_avatars.yml', __dir__))
|
|
7
|
+
.transform_values { Regexp.new(it.join('|'), Regexp::IGNORECASE) }.freeze
|
|
8
|
+
|
|
9
|
+
# The engine types that turn no propeller and no rotor of their own.
|
|
10
|
+
JETS = %w[turbo_jet turbo_fan].freeze
|
|
11
|
+
|
|
12
|
+
# The families a single turboprop keeps (an Air Tractor), rather than being drawn as one.
|
|
13
|
+
TURBOPROP_FAMILIES = %w[ag-plane fixed-wing-drone].freeze
|
|
14
|
+
|
|
15
|
+
# @param type [String] the registry's type of aircraft: "fixed_wing_single_engine", "rotorcraft"…
|
|
16
|
+
# @param engine [String, nil] the registry's type of engine: "reciprocating", "electric"…
|
|
17
|
+
# @param name [String, nil] its make and model, "Piper PA-18-150".
|
|
18
|
+
# @param engines [Integer, nil] how many engines its model has.
|
|
19
|
+
# @param seats [Integer, nil] how many seats its model has.
|
|
20
|
+
# @param airliner [Boolean] whether its model is an airliner (30 seats or more, or its freighter).
|
|
21
|
+
# @return [String] the avatar's name in the sprite, without its a-: "high-wing-tailwheel".
|
|
22
|
+
def self.for(type:, engine: nil, name: nil, engines: nil, seats: nil, airliner: false)
|
|
23
|
+
case type.to_s
|
|
24
|
+
when 'balloon', 'blimp' then 'balloon'
|
|
25
|
+
when 'glider' then 'glider'
|
|
26
|
+
when 'weight_shift_control' then 'trike'
|
|
27
|
+
when 'powered_parachute' then 'powered-parachute'
|
|
28
|
+
when 'hybrid_lift' then 'fixed-wing-drone'
|
|
29
|
+
when 'rotorcraft', 'gyroplane' then rotorcraft(engine, engines.to_i, seats.to_i)
|
|
30
|
+
when 'fixed_wing_multi_engine' then multi_engine(engine, airliner)
|
|
31
|
+
else single_engine engine, name.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def rotorcraft(engine, engines, seats)
|
|
39
|
+
return 'multirotor-drone' if engine == 'electric'
|
|
40
|
+
|
|
41
|
+
engines > 1 || seats > 8 ? 'heavy-helicopter' : 'helicopter'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def multi_engine(engine, airliner)
|
|
45
|
+
return 'twin' unless JETS.include? engine
|
|
46
|
+
|
|
47
|
+
airliner ? 'airliner' : 'business-jet'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def single_engine(engine, name)
|
|
51
|
+
return 'business-jet' if JETS.include? engine
|
|
52
|
+
|
|
53
|
+
family = FAMILIES.find { |_, pattern| pattern.match?(name) }&.first
|
|
54
|
+
return family if family && (engine != 'turbo_prop' || TURBOPROP_FAMILIES.include?(family))
|
|
55
|
+
|
|
56
|
+
engine == 'turbo_prop' ? 'single-turboprop' : family || 'high-wing'
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# locals: (plane:, date: nil) -%>
|
|
2
|
+
<%# A plane crossing the sky behind a hero, its trail behind it, flown by easy/flyer.js, which the page includes once,
|
|
3
|
+
with easy/konami.js, its easter egg.
|
|
4
|
+
plane: its icon in the sprite, "plane" (easyFAA's airliner), "small-plane" (easyAnnual's single engine) or
|
|
5
|
+
"twin-plane" (easyFAA's accidents' twin engine).
|
|
6
|
+
date: "2026-12-25", the day to fly on instead of the visitor's today, to see a holiday's colors. -%>
|
|
7
|
+
<%= tag.div class: "sky js-sky", aria: { hidden: true }, data: { date: } do %>
|
|
8
|
+
<svg class="trail js-trail" focusable="false"></svg>
|
|
9
|
+
<span class="flyer js-flyer"><%= easy_icon plane %></span>
|
|
10
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%# locals: (id:, close:, title: nil, open: false, emoji: nil, classes: nil) -%>
|
|
2
|
+
<%# A dialog over the page, rendered as a layout around what it holds (render layout: "easy/modal" do … end), which
|
|
3
|
+
always lets its reader out: its ×, Esc or a click around it (easy/modal.js, which the page includes once), or without the script the
|
|
4
|
+
× as a link. Shown when its address's #id names it, or open: true (drawn already open, say once a form asked).
|
|
5
|
+
close: where the × leads, "#" for a dialog opened by its #id, the page without what opened it otherwise.
|
|
6
|
+
title: its heading; without one, what it holds gives its own, with the id "#{id}-title" (one per step, say).
|
|
7
|
+
classes: the app's own, beside modal, to style what it holds. -%>
|
|
8
|
+
<%= tag.div id:, class: [ "modal", ("is-open" if open), classes ], role: "dialog", aria: { modal: true, labelledby: "#{id}-title" } do %>
|
|
9
|
+
<div class="modal-box">
|
|
10
|
+
<%= link_to "×", close, class: "modal-x", aria: { label: "Close" } %>
|
|
11
|
+
<% if title %><h2 id="<%= id %>-title"><% if emoji %><span aria-hidden="true"><%= emoji %></span> <% end %><%= title %></h2><% end %>
|
|
12
|
+
<%= yield %>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%# locals: (name:, id: nil, autofocus: false, label: "6-digit code") -%>
|
|
2
|
+
<%# The 6-digit code emailed to sign in or to sign: one real field, so a pasted or autofilled code lands whole, drawn
|
|
3
|
+
over six slots by easy/pin_code.js, which the page includes once; without the script, the field itself shows.
|
|
4
|
+
name: the field's, "sign_in[pin]"; a code refused is the page's to mark, is-invalid on the .pin-code. -%>
|
|
5
|
+
<div class="pin-code">
|
|
6
|
+
<%= tag.input type: "text", name:, id:, class: "pin-code-input", required: true, autofocus:, autocomplete: "one-time-code",
|
|
7
|
+
inputmode: "numeric", pattern: "[0-9]{6}", maxlength: 6, aria: { label: } %>
|
|
8
|
+
<div class="pin-code-slots" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span></div>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<%# locals: (heading: nil, heading_id: nil, before: nil, switches_id: "switches", switches: [], after: nil, search: nil, search_label: search, sort_list: nil, sort_default: "newest") -%>
|
|
2
|
+
<%# The head of a report or list: its heading, its switches (each { state:, css:, icon:, count:, label:, off: }), with the
|
|
3
|
+
sort button at their end, and a search. before and after go above and below the switches. sort_list is the list the
|
|
4
|
+
button flips by each row's data-sort (easy/report_sort); without it the page sorts by itself. -%>
|
|
5
|
+
<%= tag.section class: "report-head", data: { sort_list:, sort_default: (sort_default if sort_list) }.compact do %>
|
|
6
|
+
<% if heading %><%= tag.h2 heading, id: heading_id %><% end %>
|
|
7
|
+
<%= before %>
|
|
8
|
+
<% if switches.any? %>
|
|
9
|
+
<%= tag.ul class: "switches levels", id: switches_id do %>
|
|
10
|
+
<% switches.each do |item| %>
|
|
11
|
+
<%= tag.li class: item[:css], data: { state: item[:state], off: item[:off] }.compact do %><% if item[:icon] %><%= easy_icon item[:icon] %><% end %><span class="n"><%= item[:count] %></span> <%= item[:label] %><% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<li class="sort-button-item" hidden><button type="button" class="sort-button js-sort"><%= easy_icon sort_default %><span class="js-sort-label"><%= sort_default.capitalize %> first</span></button></li>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<%= after %>
|
|
17
|
+
<% if search %><label class="word-search report-search js-words" hidden><%= easy_icon "search" %><input type="search" placeholder="<%= search %>" aria-label="<%= search_label %>" autocomplete="off" spellcheck="false"></label><% end %>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if sort_list %><%= javascript_include_tag "easy/report_sort", defer: true %><% end %>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<%# locals: (code:, brand:, site:, logo:, purpose:, lifetime:, ignore:, link: nil, link_label: nil, link_lede: nil, fine: nil) -%>
|
|
2
|
+
<%# The email carrying a 6-digit code, the same from every easy app (its text twin: _code.text.erb): the logo, then a
|
|
3
|
+
card whose navy head holds the code large enough to read across the room, selected whole by one click or tap.
|
|
4
|
+
The code is plain digits next to the word "code", in the subject too ("123456 is your easyFAA code"), with no
|
|
5
|
+
other run of digits near it: what Apple Mail, iOS and Gmail look for to offer it to the field that asks for it.
|
|
6
|
+
code: "123456"; brand: "easyFAA"; site: the app's address, under the card; logo: the URL of the brand's logo,
|
|
7
|
+
an inline attachment so it shows wherever the email is read; purpose: "to sign in", "to sign the airframe
|
|
8
|
+
logbook entry for N22196"; lifetime: "10 minutes"; ignore: what to do with an email nobody asked for.
|
|
9
|
+
link, link_label, link_lede: a button doing what the code does, the sentence above it; fine: under it (HTML). -%>
|
|
10
|
+
<% heading = %(font-family:"Outfit","Helvetica Neue",Helvetica,Arial,sans-serif;) %>
|
|
11
|
+
<% body = %(font-family:-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;) %>
|
|
12
|
+
<% host = URI(site).host %>
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
+
<meta name="color-scheme" content="light">
|
|
19
|
+
<meta name="supported-color-schemes" content="light">
|
|
20
|
+
<meta name="format-detection" content="telephone=no, date=no, address=no">
|
|
21
|
+
<title><%= code %> is your <%= brand %> code</title>
|
|
22
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700&display=swap" rel="stylesheet">
|
|
23
|
+
</head>
|
|
24
|
+
<body style="margin:0;padding:0;background:#f1f2f3;">
|
|
25
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0;">Your <%= brand %> code <%= purpose %>: <%= code %></div>
|
|
26
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f2f3;">
|
|
27
|
+
<tr><td align="center" style="padding:32px 16px;">
|
|
28
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:560px;">
|
|
29
|
+
|
|
30
|
+
<tr><td style="padding:0 4px 20px;">
|
|
31
|
+
<a href="<%= site %>" style="text-decoration:none;"><img src="<%= logo %>" height="32" alt="<%= brand %>" style="display:block;border:0;height:32px;width:auto;"></a>
|
|
32
|
+
</td></tr>
|
|
33
|
+
|
|
34
|
+
<tr><td style="background:#ffffff;border:1px solid #c6cbcf;border-radius:18px;overflow:hidden;">
|
|
35
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
36
|
+
<tr><td align="center" style="background:#0b1e3a;padding:30px 28px 30px;border-radius:17px 17px 0 0;">
|
|
37
|
+
<div style="<%= body %>font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#9fb6d4;margin:0 0 12px;">Your <%= brand %> code</div>
|
|
38
|
+
<div style="<%= heading %>font-size:56px;font-weight:700;letter-spacing:.14em;color:#ffffff;line-height:1;-webkit-user-select:all;user-select:all;"><%= code %></div>
|
|
39
|
+
<div style="<%= body %>font-size:15px;line-height:1.4;color:#cfe0f3;margin:14px 0 0;"><%= purpose.upcase_first %></div>
|
|
40
|
+
</td></tr>
|
|
41
|
+
|
|
42
|
+
<% if link %>
|
|
43
|
+
<tr><td align="center" style="padding:22px 28px 8px;<%= body %>font-size:16px;line-height:1.5;color:#2c2f32;"><%= link_lede %></td></tr>
|
|
44
|
+
<tr><td align="center" style="padding:10px 28px 18px;">
|
|
45
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>
|
|
46
|
+
<td style="background:#1a70d0;border-radius:24px;">
|
|
47
|
+
<a href="<%= link %>" style="display:inline-block;padding:14px 30px;<%= heading %>font-size:17px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:24px;"><%= link_label %></a>
|
|
48
|
+
</td>
|
|
49
|
+
</tr></table>
|
|
50
|
+
</td></tr>
|
|
51
|
+
<% end %>
|
|
52
|
+
<tr><td align="center" style="padding:<%= link ? "0" : "22px" %> 28px 26px;<%= body %>font-size:13px;line-height:1.5;color:#636c74;">
|
|
53
|
+
<% if fine %><%= fine %><br><% end %>
|
|
54
|
+
It works once, for <%= lifetime %>. <%= ignore %>
|
|
55
|
+
</td></tr>
|
|
56
|
+
</table>
|
|
57
|
+
</td></tr>
|
|
58
|
+
|
|
59
|
+
<tr><td style="padding:18px 8px 0;<%= body %>font-size:12px;line-height:1.5;color:#636c74;text-align:center;">
|
|
60
|
+
<a href="<%= site %>" style="color:#636c74;"><%= host %></a>
|
|
61
|
+
</td></tr>
|
|
62
|
+
|
|
63
|
+
</table>
|
|
64
|
+
</td></tr>
|
|
65
|
+
</table>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# locals: (code:, brand:, site:, purpose:, lifetime:, ignore:, logo: nil, link: nil, link_label: nil, link_lede: nil, fine: nil) -%>
|
|
2
|
+
<%# The plain-text twin of _code.html.erb: the code first, on its own line beside the word "code". fine: plain text. -%>
|
|
3
|
+
Your <%= brand %> code <%= purpose %>: <%= code %>
|
|
4
|
+
<% if link %>
|
|
5
|
+
|
|
6
|
+
<%= link_lede %>
|
|
7
|
+
<%= link %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if fine %>
|
|
10
|
+
|
|
11
|
+
<%= fine %>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
It works once, for <%= lifetime %>. <%= ignore %>
|
|
15
|
+
|
|
16
|
+
<%= site %>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<%# The page every easy app draws: an app's own layout fills the slots, then renders this one
|
|
2
|
+
(render template: "layouts/easy/site").
|
|
3
|
+
- :easy_head, its title, metas and links;
|
|
4
|
+
- :easy_stylesheets, its own stylesheets, after the shared ones;
|
|
5
|
+
- :body_class, the body's classes;
|
|
6
|
+
- :site_brand_url and :site_brand, where its logo goes and the logo;
|
|
7
|
+
- :site_nav, its links, sign in or out;
|
|
8
|
+
- :sky_band, "full" or "slim", for the band under the header; then the page is wide;
|
|
9
|
+
- :page_class, the page's own classes beside page ("wide" for one that lays out its own widths);
|
|
10
|
+
- :site_footer, its footer's lines and links;
|
|
11
|
+
- :body_end, its scripts. -%>
|
|
12
|
+
<!doctype html>
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17
|
+
<%= yield :easy_head %>
|
|
18
|
+
<%# The headings' face, asked for with the stylesheet rather than once it is read (easy/base.css). -%>
|
|
19
|
+
<%= preload_link_tag asset_path("easy/outfit-latin.woff2"), as: "font", type: "font/woff2", crossorigin: "anonymous" %>
|
|
20
|
+
<%= stylesheet_link_tag "easy/tokens", "easy/base", "easy/layout", "easy/components" %>
|
|
21
|
+
<%= yield :easy_stylesheets %>
|
|
22
|
+
<script>(function(){document.documentElement.className+=' has-js';try{var t=localStorage.getItem('theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t)}}catch(e){}})();</script>
|
|
23
|
+
</head>
|
|
24
|
+
<body class="<%= content_for(:body_class) %>">
|
|
25
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
26
|
+
<header class="site-header">
|
|
27
|
+
<div class="page-width">
|
|
28
|
+
<%= link_to content_for(:site_brand_url) || "/", class: "site-brand" do %><%= yield :site_brand %><% end %>
|
|
29
|
+
<div>
|
|
30
|
+
<nav class="site-nav" aria-label="Site"><%= yield :site_nav %></nav>
|
|
31
|
+
<button type="button" class="theme-toggle js-theme" aria-pressed="false">
|
|
32
|
+
<%= easy_icon "sun", class: "i i-sun" %><%= easy_icon "moon", class: "i i-moon" %><span class="theme-toggle-label">Dark theme</span>
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
37
|
+
<% if content_for?(:sky_band) %>
|
|
38
|
+
<div class="sky-band<%= " slim" if content_for(:sky_band) == "slim" %>"></div>
|
|
39
|
+
<% end %>
|
|
40
|
+
<main id="main" class="page<%= " wide" if content_for?(:sky_band) %><%= " #{content_for(:page_class)}" if content_for?(:page_class) %>">
|
|
41
|
+
<%= yield %>
|
|
42
|
+
</main>
|
|
43
|
+
<footer id="footer" class="site-footer">
|
|
44
|
+
<div class="site-footer-inner">
|
|
45
|
+
<%= yield :site_footer %>
|
|
46
|
+
</div>
|
|
47
|
+
</footer>
|
|
48
|
+
<%= yield :body_end %>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
<meta name="color-scheme" content="light dark">
|
|
7
|
+
<meta name="robots" content="noindex">
|
|
8
|
+
<title><%= content_for(:title) || @site_name %></title>
|
|
9
|
+
<style>
|
|
10
|
+
:root{color-scheme:light;--bg:#ffffff;--ink:#15202e;--muted:#50575d;--primary:#1a70d0}
|
|
11
|
+
@media (prefers-color-scheme: dark){:root{color-scheme:dark;--bg:#131415;--ink:#f3f5f8;--muted:#abb2b8;--primary:#689dff}}
|
|
12
|
+
*{box-sizing:border-box}
|
|
13
|
+
body{margin:0;min-height:100svh;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--bg);color:var(--ink);font:500 17px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;text-align:center;padding:40px 24px}
|
|
14
|
+
a{color:var(--primary)}
|
|
15
|
+
h1{display:flex;align-items:center;justify-content:center;font-size:clamp(1.5rem,5.5vw,2.25rem);margin:0}
|
|
16
|
+
h1 svg{width:1.1em;height:1.1em;margin-right:0.3em;flex-shrink:0}
|
|
17
|
+
p{color:var(--muted);margin:18px 0 0;max-width:32rem}
|
|
18
|
+
footer{margin-top:40px;font-size:15px;color:var(--muted)}
|
|
19
|
+
footer a{color:inherit;text-decoration:none}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<%= yield %>
|
|
24
|
+
<footer><%= link_to @site_name, @site_url %></footer>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<% content_for :title, "#{@heading} | #{@site_name}" %>
|
|
2
|
+
<h1>
|
|
3
|
+
<svg viewBox="0 0 64 64" aria-hidden="true">
|
|
4
|
+
<g stroke="currentColor" stroke-width="2.5" stroke-linejoin="round">
|
|
5
|
+
<polygon points="60,8 4,30 25,37" fill="#ffffff"/>
|
|
6
|
+
<polygon points="60,8 25,37 31,57" fill="#cfe0f3"/>
|
|
7
|
+
</g>
|
|
8
|
+
<g stroke="#7a93b3" stroke-width="1.6" stroke-linecap="round">
|
|
9
|
+
<line x1="16" y1="28.5" x2="36" y2="21"/><line x1="21" y1="31.5" x2="44" y2="22.5"/><line x1="27" y1="33" x2="46" y2="25.5"/>
|
|
10
|
+
</g>
|
|
11
|
+
<path d="M10 44 C14 50 18 53 24 55" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3" stroke-linecap="round"/>
|
|
12
|
+
</svg>
|
|
13
|
+
<%= @heading %>
|
|
14
|
+
</h1>
|
|
15
|
+
<p><%= @message %></p>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# The single-engine airplane families easyFAA tells apart by make and model ("Cessna 172N",
|
|
2
|
+
# "Piper PA-18-150", "Van's RV-8"), tried in this order, the first pattern found in the name
|
|
3
|
+
# winning. A name no pattern finds is drawn high-wing, as the commonest. Case is ignored.
|
|
4
|
+
fixed-wing-drone:
|
|
5
|
+
- v-?bat|zipline|\buav\b|\buas\b|unmanned|drone
|
|
6
|
+
biplane:
|
|
7
|
+
- stearman|\bpt-?1[37]\b|\bn2s\b|pitts|\bs-?[12][abcs]?\b|waco|great lakes|starduster|skybolt|acro ?sport
|
|
8
|
+
- hatz|miniplane|christen|eagle ii|\bultimate\b|tiger moth|\bjungmann
|
|
9
|
+
ag-plane:
|
|
10
|
+
- air tractor|\bat-?[3-8]\d\d|thrush|\bs-?2r|ag-?cat|g-?164|\bpa-?25|pawnee|\bpa-?36|ag ?(wagon|truck|husky)|\b188\b
|
|
11
|
+
floatplane:
|
|
12
|
+
- \blake\b|\bla-?(4|250)|searey|sea rey|icon a5|seawind|\bg-?(21|44|73)\b|goose|widgeon|seabee|rc-?3\b|skimmer|catalina
|
|
13
|
+
low-wing-tailwheel:
|
|
14
|
+
- \brv-?[3468]\b(?!a)|\bat-?6\b|\bt-?6\b|\bsnj\b|harvard|\bp-?51\b|mustang|\bpt-?(19|22|26)\b|\bextra\b|\bea-?300
|
|
15
|
+
- edge 540|\bgiles\b|\blaser\b|\bcap ?(10|21|23)|\bsu-?(26|29|31)\b|\bt-?28\b
|
|
16
|
+
high-wing-tailwheel:
|
|
17
|
+
- \bj-?[2345]\b|\bpa-?(11|12|14|16|17|18|20)\b|cub|aeronca|champ|citabria|decathlon|scout|\b7[acegk]c|\b8kcab
|
|
18
|
+
- taylorcraft|\bbc-?12|cessna (a)?1(20|40|70|80|85|95)|\b(a)?185|maule|husky|\baviat\b|stinson|luscombe|kitfox|savage
|
|
19
|
+
- \bm-?[4-7]\b|helio|courier|\bdhc-?2\b|beaver|\bl-?(2|3|4|16|19)\b|bird ?dog|super ?cub|carbon ?cub|backcountry
|
|
20
|
+
low-wing-retractable:
|
|
21
|
+
- mooney|\bm20|(beech|raytheon|textron|hawker).*\b[a-v]?(33|35|36)[a-z]?\b|bonanza|debonair|\bpa-?(24|28r|30|32r|39|46)\b|comanche|arrow|saratoga
|
|
22
|
+
- lance|\bt-?34\b|meridian|malibu|mirage|navion|\bm-?18|\bb-?24r|sierra|swift|\bgc-?1|viking|\b17-?3\d|bellanca 14
|
|
23
|
+
low-wing:
|
|
24
|
+
- \bpa-?(28|32|38)\b|cherokee|warrior|archer|cadet|tomahawk|cirrus|\bsr-?2[02]|\bda-?(20|40)|diamond|katana
|
|
25
|
+
- \baa-?(1|5)|grumman|tiger|cheetah|traveler|musketeer|sundowner|\bbe-?23|\bc-?23\b|\brv-?\d+|ercoupe|aircoupe|\balon\b
|
|
26
|
+
- lancair|glasair|sonex|zenith|\bch-?[67]\d\d|tecnam|evektor|sportstar|\bsr-?20
|
data/lib/wordmark/engine.rb
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
require 'rails'
|
|
3
3
|
|
|
4
4
|
module Wordmark
|
|
5
|
-
# Hooks the gem into a host app: serves its files, its error pages and its helpers.
|
|
5
|
+
# Hooks the gem into a host app: serves its files, its error pages and its helpers. The easy
|
|
6
|
+
# design system's stylesheets, scripts, fonts and icons are under `app/assets`, which the
|
|
7
|
+
# host's asset pipeline reads as its own: `stylesheet_link_tag "easy/tokens"`.
|
|
6
8
|
class Engine < ::Rails::Engine
|
|
7
9
|
# The folders the design site publishes, served from this gem at the same paths so a
|
|
8
10
|
# page on an app and a page on the site link one file. Each keeps its slash:
|
|
@@ -25,20 +27,21 @@ module Wordmark
|
|
|
25
27
|
header_rules: HEADERS, cascade: true
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
# The
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
# The same page for 400, 404, 422 and 500 on every app (Wordmark::ErrorsController), unless
|
|
31
|
+
# the host has already named an exceptions app of its own, and the page it shows while it
|
|
32
|
+
# is down at the path Heroku and a reader expect.
|
|
33
|
+
initializer 'wordmark.errors' do |app|
|
|
34
|
+
app.config.exceptions_app ||= app.routes
|
|
35
|
+
app.routes.prepend do
|
|
36
|
+
%w[400 404 422 500].each do |status|
|
|
37
|
+
get status, to: 'wordmark/errors#show', defaults: { status: status.to_i }
|
|
38
|
+
end
|
|
39
|
+
get 'down.html', to: 'wordmark/errors#down', format: false
|
|
40
|
+
end
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
initializer 'wordmark.helpers' do
|
|
41
|
-
ActiveSupport.on_load(:action_view) { include Wordmark::Helpers }
|
|
44
|
+
ActiveSupport.on_load(:action_view) { include Wordmark::Helpers, Wordmark::EasyHelper }
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
end
|
data/lib/wordmark/version.rb
CHANGED
data/lib/wordmark.rb
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
require 'bh'
|
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
2
3
|
require_relative 'wordmark/version'
|
|
3
4
|
require_relative 'wordmark/marks'
|
|
4
5
|
require_relative 'wordmark/copy'
|
|
5
|
-
require_relative 'wordmark/down'
|
|
6
6
|
require_relative 'wordmark/helpers'
|
|
7
7
|
require_relative 'wordmark/engine'
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# easyFAA's front end: the easy design system every app's pages are drawn with (its layout,
|
|
10
|
+
# stylesheets, scripts, fonts, icons and partials), the error pages every app shows, the marks
|
|
11
|
+
# every app is known by, and the helpers that draw them the same way everywhere.
|
|
12
12
|
module Wordmark
|
|
13
|
+
# The site an error page names, in its title, its one link and the maintenance message, as
|
|
14
|
+
# `[name, url]` for the request: an app that is not easyFAA's own names itself,
|
|
15
|
+
# `Wordmark.site = ->(request) { ['easyAnnual', 'https://easyannual.com/'] }`.
|
|
16
|
+
mattr_accessor :site, default: ->(_request) { ['easyFAA', 'https://easyfaa.com/'] }
|
|
17
|
+
|
|
13
18
|
# The form every easyfaa page is written with, which is bh's: the house dresses no
|
|
14
19
|
# field differently, so this name and `Bh::FormBuilder` are one builder.
|
|
15
20
|
FormBuilder = Bh::FormBuilder
|
|
@@ -20,7 +25,7 @@ module Wordmark
|
|
|
20
25
|
|
|
21
26
|
# Which of the family this host is, read off the Rails application's own name — the
|
|
22
27
|
# `Tower` of `Tower::Application` — and the house itself for any app not in `MARKS`.
|
|
23
|
-
# What a tab wears
|
|
28
|
+
# What a tab wears and what a logo says.
|
|
24
29
|
def self.app
|
|
25
30
|
name = Rails.application.class.module_parent_name.downcase.to_sym
|
|
26
31
|
MARKS.key?(name) ? name : :easyfaa
|
data/public/_headers
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
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
|
|
2
|
+
# origins, both of which the browser refuses without this. Cloudflare adds no CORS.
|
|
3
3
|
/*
|
|
4
4
|
Access-Control-Allow-Origin: *
|
|
5
5
|
|
|
6
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
|
|
8
|
-
#
|
|
7
|
+
# load and is answered 304 while it stands, so a page drawn against a new bundle never runs
|
|
8
|
+
# the old one.
|
|
9
9
|
/css/*
|
|
10
10
|
Cache-Control: public, max-age=0, must-revalidate
|
|
11
11
|
/js/*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
Cache-Control: public, max-age=0, must-revalidate
|
|
19
19
|
|
|
20
20
|
# A font's name carries a hash of its bytes, so a browser may keep it for good; everything
|
|
21
|
-
# else keeps
|
|
21
|
+
# else keeps Cloudflare's default, which is to revalidate on every load and answer 304 while it
|
|
22
22
|
# stands, so the URL of a file never has to change between versions.
|
|
23
23
|
/font/*
|
|
24
24
|
Cache-Control: public, max-age=31536000, immutable
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wordmark
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- claudiob
|
|
@@ -77,8 +77,33 @@ files:
|
|
|
77
77
|
- CHANGELOG.md
|
|
78
78
|
- MIT-LICENSE
|
|
79
79
|
- README.md
|
|
80
|
+
- app/assets/fonts/easy/outfit-latin-ext.woff2
|
|
81
|
+
- app/assets/fonts/easy/outfit-latin.woff2
|
|
82
|
+
- app/assets/images/easy/sprite.svg
|
|
83
|
+
- app/assets/javascripts/easy/flyer.js
|
|
84
|
+
- app/assets/javascripts/easy/konami.js
|
|
85
|
+
- app/assets/javascripts/easy/modal.js
|
|
86
|
+
- app/assets/javascripts/easy/pin_code.js
|
|
87
|
+
- app/assets/javascripts/easy/report_sort.js
|
|
88
|
+
- app/assets/javascripts/easy/rotating_placeholder.js
|
|
89
|
+
- app/assets/stylesheets/easy/base.css
|
|
90
|
+
- app/assets/stylesheets/easy/components.css
|
|
91
|
+
- app/assets/stylesheets/easy/layout.css
|
|
92
|
+
- app/assets/stylesheets/easy/tokens.css
|
|
93
|
+
- app/controllers/wordmark/errors_controller.rb
|
|
94
|
+
- app/helpers/wordmark/easy_helper.rb
|
|
95
|
+
- app/models/aircraft_avatar.rb
|
|
96
|
+
- app/views/easy/_flyer.html.erb
|
|
97
|
+
- app/views/easy/_modal.html.erb
|
|
98
|
+
- app/views/easy/_pin_code.html.erb
|
|
99
|
+
- app/views/easy/_report_head.html.erb
|
|
100
|
+
- app/views/easy/mailer/_code.html.erb
|
|
101
|
+
- app/views/easy/mailer/_code.text.erb
|
|
102
|
+
- app/views/layouts/easy/site.html.erb
|
|
80
103
|
- app/views/layouts/easyfaa.html.erb
|
|
104
|
+
- app/views/layouts/wordmark/errors.html.erb
|
|
81
105
|
- app/views/recourses/_head.html.erb
|
|
106
|
+
- app/views/wordmark/errors/show.html.erb
|
|
82
107
|
- art/blackbox.svg
|
|
83
108
|
- art/cube.svg
|
|
84
109
|
- art/data.svg
|
|
@@ -97,10 +122,10 @@ files:
|
|
|
97
122
|
- art/stockroom.svg
|
|
98
123
|
- art/tower.svg
|
|
99
124
|
- art/wordmark.svg
|
|
125
|
+
- config/aircraft_avatars.yml
|
|
100
126
|
- config/locales/easyfaa.en.yml
|
|
101
127
|
- lib/wordmark.rb
|
|
102
128
|
- lib/wordmark/copy.rb
|
|
103
|
-
- lib/wordmark/down.rb
|
|
104
129
|
- lib/wordmark/engine.rb
|
|
105
130
|
- lib/wordmark/helpers.rb
|
|
106
131
|
- lib/wordmark/helpers/flows.rb
|
|
@@ -327,7 +352,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
327
352
|
requirements:
|
|
328
353
|
- - ">="
|
|
329
354
|
- !ruby/object:Gem::Version
|
|
330
|
-
version: 3.
|
|
355
|
+
version: '3.4'
|
|
331
356
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
332
357
|
requirements:
|
|
333
358
|
- - ">="
|
data/lib/wordmark/down.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
require 'rack/files'
|
|
2
|
-
|
|
3
|
-
module Wordmark
|
|
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' => "/#{Wordmark.app}.html")
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|