bingo: serve real favicon files

The favicon was an emoji in a data: URI and /favicon.ico was a 404.
Google only shows a site icon in results when the favicon has a
crawlable URL, and the emoji looked different on every platform.

Add an SVG (a 5x5 card with a crossed-out diagonal), a 16/32/48 ICO
and a 180px apple-touch-icon under assets/bingo/, served by explicit
routes because public/ belongs to asxp.io.

Assisted-by: Claude:opus-5
This commit is contained in:
Sergei Poljanski 2026-10-10 09:27:44 +04:00
commit cb2eba1a26
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
6 changed files with 25 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
assets/bingo/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
assets/bingo/favicon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#121212"/><rect x="5" y="5" width="10" height="10" rx="1.5" fill="#ff6a4d"/><rect x="16" y="5" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="27" y="5" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="38" y="5" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="49" y="5" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="5" y="16" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="16" y="16" width="10" height="10" rx="1.5" fill="#ff6a4d"/><rect x="27" y="16" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="38" y="16" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="49" y="16" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="5" y="27" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="16" y="27" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="27" y="27" width="10" height="10" rx="1.5" fill="#ff6a4d"/><rect x="38" y="27" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="49" y="27" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="5" y="38" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="16" y="38" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="27" y="38" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="38" y="38" width="10" height="10" rx="1.5" fill="#ff6a4d"/><rect x="49" y="38" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="5" y="49" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="16" y="49" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="27" y="49" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="38" y="49" width="10" height="10" rx="1.5" fill="#ece7dc"/><rect x="49" y="49" width="10" height="10" rx="1.5" fill="#ff6a4d"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -111,6 +111,16 @@ class BingoWeb < Sinatra::Base
erb :index
end
# Icons from assets/bingo/ (public/ is asxp.io's). Search engines want
# a crawlable favicon URL; a data: URI doesn't count.
ICONS = { 'favicon.svg' => 'image/svg+xml', 'favicon.ico' => 'image/x-icon',
'apple-touch-icon.png' => 'image/png' }.freeze
get %r{/(favicon\.svg|favicon\.ico|apple-touch-icon\.png)} do |name|
cache_control :public, max_age: 86_400
send_file File.join(settings.root, 'assets', 'bingo', name), type: ICONS[name]
end
# Photos are immutable per key (a new upload gets a new key).
get '/p/:file' do
bytes, type = Bingo::Photo.read(params[:file])

View file

@ -190,6 +190,17 @@ class BingoTest < Minitest::Test
assert_equal 'http://example.org/en?view=operational', last_response.location
end
def test_icons
{ '/favicon.svg' => 'image/svg+xml', '/favicon.ico' => 'image/x-icon',
'/apple-touch-icon.png' => 'image/png' }.each do |path, type|
get path
assert last_response.ok?, path
assert_equal type, last_response.content_type
end
get '/en'
assert_includes last_response.body, '<link rel="icon" href="/favicon.svg" type="image/svg+xml">'
end
def test_root_varies_on_language
get '/'
assert_equal 'Accept-Language', last_response.headers['Vary']

View file

@ -34,7 +34,9 @@
<meta name="twitter:title" content="<%= t(:seo_title) %>">
<meta name="twitter:description" content="<%= desc %>">
<script type="application/ld+json"><%== json_ld(@board, desc) %></script>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎱</text></svg>">
<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<style>
:root {
--bg: #f4f1ea; --card: #fffdf8; --ink: #1d1b18; --muted: #6f6a60; --line: #d9d3c6;