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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +47 -15
  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/config/aircraft_avatars.yml +26 -0
  30. data/lib/wordmark/engine.rb +15 -12
  31. data/lib/wordmark/version.rb +1 -1
  32. data/lib/wordmark.rb +10 -5
  33. data/public/_headers +4 -4
  34. metadata +28 -3
  35. 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&amp;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
@@ -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 branded error pages, read by status from `public/page` the way Rails reads a host's
29
- # own `public/`, unless the host has already named an app of its own.
30
- initializer 'wordmark.exceptions' do |app|
31
- app.config.exceptions_app ||=
32
- ActionDispatch::PublicExceptions.new Engine.root.join('public/page').to_s
33
- end
34
-
35
- # The page a host shows while it is down, at the path Heroku and a reader expect.
36
- initializer 'wordmark.down' do |app|
37
- app.middleware.insert_before Rails::Rack::Logger, Down
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
@@ -1,4 +1,4 @@
1
1
  module Wordmark
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '0.22.0'
3
+ VERSION = '0.23.0'
4
4
  end
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
- # easyfaa's front end: the stylesheet and script every page loads — bh's Bootstrap 6
10
- # with the house's own layer over it — the marks every app is known by, and the helpers that
11
- # draw a head, a logo or a signup page the same way everywhere.
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, what a logo says and whose page answers `/down.html`.
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 Pages adds no CORS.
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. Pages would otherwise let one be kept for four
8
- # hours, and a page drawn against a new bundle would run the old one until they were up.
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 Pages' default, which is to revalidate on every load and answer 304 while it
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.22.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.2.0
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