The datacenter text said "No. 33 by power" for a site that is 25th on the default card, because it counted construction projects. Rank among operational datacenters only, matching the card, and leave the rank out for sites not yet in service. data.json and status.json gain an operational_rank next to the list position. The status page now lists, under each site, the hosts it probes with their label, last result and the source that ties them to the site, folded away behind a click; the control hosts in Russia get the same list, and datacenter pages show it under their reachability block. status.json carries the same targets. For search: a keyword title tagline and meta description, WebPage and Dataset structured data (pointing at status.json), x-default, and the status pages in the sitemap. Assisted-by: Claude:opus-5-5
192 lines
12 KiB
Text
192 lines
12 KiB
Text
<% base = base_url %>
|
|
<% og_img = @og && "#{base}/p/#{@og.og_file}" %>
|
|
<!doctype html>
|
|
<html lang="<%= @locale %>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><%= @title %></title>
|
|
<meta name="description" content="<%= @desc %>">
|
|
<meta name="robots" content="<%= @noindex ? 'noindex, follow' : 'index, follow, max-image-preview:large' %>">
|
|
<meta name="theme-color" content="#121212">
|
|
<link rel="canonical" href="<%= base %><%= @path_for.(@locale) %>">
|
|
<% Bingo::LOCALES.each do |l| %>
|
|
<link rel="alternate" hreflang="<%= l %>" href="<%= base %><%= @path_for.(l) %>">
|
|
<% end %>
|
|
<% if @x_default %>
|
|
<link rel="alternate" hreflang="x-default" href="<%= base %><%= @x_default %>">
|
|
<% end %>
|
|
<link rel="sitemap" type="application/xml" href="/sitemap.xml">
|
|
<link rel="alternate" type="application/rss+xml" title="<%= format(t(:feed_title), site: t(:title)) %>" href="<%= base %>/<%= @locale %>/feed.xml">
|
|
<link rel="alternate" type="application/feed+json" title="<%= format(t(:feed_title), site: t(:title)) %>" href="<%= base %>/<%= @locale %>/feed.json">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="<%= t(:title) %>">
|
|
<meta property="og:title" content="<%= @title %>">
|
|
<meta property="og:description" content="<%= @desc %>">
|
|
<meta property="og:url" content="<%= base %><%= @path_for.(@locale) %>">
|
|
<meta property="og:locale" content="<%= t(:og_locale) %>">
|
|
<% (Bingo::LOCALES - [@locale]).each do |l| %>
|
|
<meta property="og:locale:alternate" content="<%= Bingo::T[l][:og_locale] %>">
|
|
<% end %>
|
|
<% if og_img %>
|
|
<meta property="og:image" content="<%= og_img %>">
|
|
<% if @og.photo_og %>
|
|
<meta property="og:image:type" content="image/jpeg">
|
|
<meta property="og:image:width" content="<%= Bingo::Photo::OG_SIZE[0] %>">
|
|
<meta property="og:image:height" content="<%= Bingo::Photo::OG_SIZE[1] %>">
|
|
<% end %>
|
|
<meta property="og:image:alt" content="<%= @og.l(:name, @locale) %>, <%= @og.l(:city, @locale) %>">
|
|
<meta name="twitter:image" content="<%= og_img %>">
|
|
<% end %>
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="<%= @title %>">
|
|
<meta name="twitter:description" content="<%= @desc %>">
|
|
<script type="application/ld+json"><%== @ld %></script>
|
|
<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;
|
|
--hit: #c8361d; --hit-bg: #fbe3dc; --win: #e7b416; --on-win: #1d1b18; --up: #2f7d32;
|
|
--shade: rgb(0 0 0 / .62); --on-photo: #fff; --on-photo-muted: rgb(255 255 255 / .75);
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--bg: #121212; --card: #1c1b19; --ink: #ece7dc; --muted: #9a9384; --line: #34312b;
|
|
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a; --up: #5cc066;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--bg: #121212; --card: #1c1b19; --ink: #ece7dc; --muted: #9a9384; --line: #34312b;
|
|
--hit: #ff6a4d; --hit-bg: #3a1d16; --win: #f0c43a; --up: #5cc066;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
|
|
main { max-width: 980px; margin: 0 auto; padding: 24px 16px 48px; }
|
|
a { color: inherit; }
|
|
header { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px 16px; margin-bottom: 16px; }
|
|
h1 { margin: 0; font-size: clamp(28px, 6vw, 44px); letter-spacing: -.02em; }
|
|
h1 span { color: var(--hit); }
|
|
h1 small { display: block; margin-top: 4px; font-size: 16px; font-weight: 600; letter-spacing: 0; color: var(--muted); }
|
|
.switches { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
.toggle { display: flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
|
|
.toggle a { padding: 2px 10px; font-size: 13px; text-decoration: none; }
|
|
.toggle a + a { border-left: 1px solid var(--line); }
|
|
.toggle a[aria-current] { background: var(--hit); color: var(--bg); }
|
|
.langs { display: flex; gap: 4px; }
|
|
.langs a { padding: 2px 8px; border: 1px solid var(--line); border-radius: 6px; text-decoration: none; font-size: 13px; text-transform: uppercase; }
|
|
.langs a[aria-current] { background: var(--ink); color: var(--bg); border-color: var(--ink); }
|
|
.meta { color: var(--muted); font-size: 14px; }
|
|
.meta b { color: var(--ink); font-variant-numeric: tabular-nums; }
|
|
.lead { color: var(--muted); margin: 0 0 16px; max-width: 60ch; }
|
|
.banner { margin: 0 0 16px; padding: 12px 16px; border-radius: 10px; background: var(--win); color: var(--on-win); font-weight: 700; font-size: 18px; }
|
|
|
|
.letters, .grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
|
|
.cell.empty { background: transparent; border-style: dashed; cursor: default; }
|
|
.providers { margin: 0 0 16px; }
|
|
.providers fieldset { border: 0; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
.providers legend { font-size: 13px; color: var(--muted); margin-bottom: 6px; padding: 0; }
|
|
.providers label { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border: 1px solid var(--line); border-radius: 999px; font-size: 13px; cursor: pointer; white-space: nowrap; }
|
|
.providers label:has(input:checked) { border-color: var(--ink); }
|
|
.providers input { accent-color: var(--hit); margin: 0; }
|
|
.providers label span { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
.provider-actions { display: flex; gap: 12px; align-items: center; margin-top: 8px; font-size: 13px; }
|
|
.provider-actions button { font: inherit; padding: 3px 12px; border-radius: 6px; border: 1px solid var(--line); background: var(--card); color: var(--ink); cursor: pointer; }
|
|
.letters div { text-align: center; font-weight: 900; font-size: clamp(22px, 5vw, 36px); color: var(--hit); }
|
|
.grid { margin-top: 6px; }
|
|
.cell {
|
|
position: relative; aspect-ratio: 1; padding: 8px; overflow: hidden; width: 100%;
|
|
background: var(--card); border: 1px solid var(--line); border-radius: 8px;
|
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
text-align: left; color: inherit; font: inherit; text-decoration: none; cursor: pointer;
|
|
}
|
|
.cell.photo { color: var(--on-photo); border-color: transparent; }
|
|
.cell .bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
|
|
.cell.photo::before { content: ""; position: absolute; inset: 0; z-index: 1; background: linear-gradient(to top, var(--shade), rgb(0 0 0 / .15) 70%); }
|
|
.cell > :not(.bg) { position: relative; z-index: 2; }
|
|
.cell .n { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
.cell.photo .n, .cell.photo .sub { color: var(--on-photo-muted); }
|
|
.cell .name { font-weight: 700; font-size: clamp(11px, 1.9vw, 15px); line-height: 1.15; }
|
|
.cell .sub { font-size: clamp(10px, 1.4vw, 12px); color: var(--muted); }
|
|
.cell .tag { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; }
|
|
.cell.hit { border-color: var(--hit); }
|
|
.cell.hit:not(.photo) { background-color: var(--hit-bg); }
|
|
.cell.hit::after {
|
|
content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none; opacity: .8;
|
|
background:
|
|
linear-gradient(to top right, transparent calc(50% - 2px), var(--hit) calc(50% - 2px), var(--hit) calc(50% + 2px), transparent calc(50% + 2px)),
|
|
linear-gradient(to top left, transparent calc(50% - 2px), var(--hit) calc(50% - 2px), var(--hit) calc(50% + 2px), transparent calc(50% + 2px));
|
|
}
|
|
.cell .stamp { position: absolute; right: 6px; top: 4px; font-size: clamp(16px, 3vw, 24px); z-index: 4; }
|
|
.cell.win { box-shadow: 0 0 0 3px var(--win); }
|
|
.cell:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
|
|
|
|
section { margin-top: 32px; }
|
|
h2 { font-size: 18px; margin: 0 0 12px; }
|
|
.log { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
|
|
.log li { display: grid; grid-template-columns: 96px 28px 1fr; gap: 8px; padding: 10px 0; border-bottom: 1px solid var(--line); }
|
|
.log time, .log .dc { color: var(--muted); }
|
|
.log .dc { font-size: 13px; }
|
|
.empty { color: var(--muted); }
|
|
|
|
dialog { width: min(520px, calc(100vw - 32px)); border: 1px solid var(--line); border-radius: 12px; background: var(--card); color: var(--ink); padding: 0; }
|
|
dialog::backdrop { background: rgb(0 0 0 / .55); }
|
|
dialog img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
|
|
dialog .body { padding: 16px 20px 20px; }
|
|
dialog h3 { margin: 0 0 4px; }
|
|
dialog .credit { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
|
dialog ul { padding-left: 18px; }
|
|
dialog .actions { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 12px; }
|
|
dialog form { margin: 0; }
|
|
.crumbs a { font-weight: 700; text-decoration: none; }
|
|
.crumbs a:hover { text-decoration: underline; }
|
|
.dc-page img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 10px; margin-bottom: 16px; }
|
|
.dc-page h1 { font-size: clamp(26px, 5vw, 38px); }
|
|
.dc-page.hit h1 { color: var(--hit); }
|
|
.dc-page .credit { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
|
.dc-page .facts { max-width: 65ch; }
|
|
.dc-page ul { padding-left: 18px; }
|
|
.pager { display: flex; justify-content: space-between; gap: 12px; margin-top: 32px; padding-top: 12px; border-top: 1px solid var(--line); }
|
|
dialog button { font: inherit; padding: 6px 14px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--ink); cursor: pointer; }
|
|
/* Reachability (status page and datacenter pages) */
|
|
.st { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
.st::before { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--line); flex: none; }
|
|
.st.up::before { background: var(--up); }
|
|
.st.degraded::before { background: var(--win); }
|
|
.st.down::before { background: var(--hit); }
|
|
.st.down { color: var(--hit); font-weight: 600; }
|
|
.status-table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
.status-table th { text-align: left; font-weight: 600; color: var(--muted); font-size: 12px; padding: 6px 8px 6px 0; border-bottom: 1px solid var(--line); }
|
|
.status-table td { padding: 8px 8px 8px 0; border-bottom: 1px solid var(--line); vertical-align: middle; }
|
|
.status-table .num { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
|
|
.status-table .city { display: block; color: var(--muted); font-size: 12px; }
|
|
.bars { display: grid; grid-template-columns: repeat(24, 1fr); gap: 2px; min-width: 120px; height: 18px; }
|
|
.bars i { border-radius: 2px; background: var(--line); }
|
|
.bars i.up { background: var(--up); }
|
|
.bars i.degraded { background: var(--win); }
|
|
.bars i.down { background: var(--hit); }
|
|
.status-banner { padding: 10px 14px; border-radius: 8px; background: var(--hit-bg); margin: 0 0 16px; max-width: 65ch; }
|
|
.unmonitored { color: var(--muted); font-size: 13px; max-width: 75ch; }
|
|
.probes { font-size: 13px; color: var(--muted); margin: 4px 0 8px; }
|
|
.probes summary { cursor: pointer; width: max-content; }
|
|
.probes ul { list-style: none; margin: 6px 0 0; padding: 0; }
|
|
.probes li { margin: 2px 0; overflow-wrap: anywhere; }
|
|
.probes code { color: var(--ink); }
|
|
.status-table .probe-row td { border-top: 0; padding-top: 0; }
|
|
.status-table tr:has(+ .probe-row) td { border-bottom: 0; padding-bottom: 2px; }
|
|
footer { margin-top: 32px; color: var(--muted); font-size: 13px; }
|
|
footer p { margin: 0 0 4px; }
|
|
@media (max-width: 560px) {
|
|
.cell { padding: 5px; border-radius: 6px; }
|
|
.cell .sub, .cell .n, .cell .tag { display: none; }
|
|
.letters, .grid { gap: 4px; }
|
|
.log li { grid-template-columns: 84px 24px 1fr; }
|
|
.status-table .hide-sm { display: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<%== yield %>
|
|
</body>
|
|
</html>
|