diff --git a/index.html b/index.html index 5a9030a..e546987 100644 --- a/index.html +++ b/index.html @@ -60,39 +60,19 @@ - -
- - Amsterdam - Amsterdam - Netherlands - - - Boston - Boston - United States - - - London - London - United Kingdom - - - Mumbai - Mumbai - India - - - São Paulo - São Paulo - Brazil - - - Sydney - Sydney - Australia - -
+ +
+
+
+ Global Server Network + + Testing all locations... + +
+
+
+
+
diff --git a/script.js b/script.js index 5f2e48c..d395d2f 100644 --- a/script.js +++ b/script.js @@ -428,6 +428,130 @@ var si = 0, renderBlog(); } + /* ---- Server Ping Banner ---- */ + (function() { + var SERVERS = [ + { host: 'meppel.odoo4projects.com', city: 'Amsterdam', region: 'Netherlands' }, + { host: 'boston.odoo4projects.com', city: 'Boston', region: 'United States' }, + { host: 'manchester.odoo4projects.com', city: 'Manchester', region: 'United Kingdom' }, + { host: 'mumbai.odoo4projects.com', city: 'Mumbai', region: 'India' }, + { host: 'saopaulo.odoo4projects.com', city: 'S\u00e3o Paulo', region: 'Brazil' }, + { host: 'sydney.odoo4projects.com', city: 'Sydney', region: 'Australia' } + ]; + + var grid = document.getElementById('serverGrid'); + var bannerStatus = document.getElementById('bannerStatus'); + var optimalEl = document.getElementById('serverOptimal'); + var cards = []; + var results = []; + var pending = SERVERS.length; + + SERVERS.forEach(function(s, idx) { + var card = document.createElement('div'); + card.className = 'server-card'; + card.innerHTML = + '
Best
' + + '
' + s.city + '
' + + '
' + s.region + '
' + + '
' + + '
' + + 'connecting' + + '
'; + grid.appendChild(card); + cards.push({ el: card, server: s, idx: idx }); + }); + + /* Stagger the card entrance animation */ + cards.forEach(function(c, i) { + setTimeout(function() { + c.el.classList.add('visible'); + }, 80 + i * 70); + }); + + function pingServer(host) { + return new Promise(function(resolve) { + var start = performance.now(); + var img = new Image(); + var timedOut = false; + var timer = setTimeout(function() { + timedOut = true; + img.src = ''; + resolve({ time: -1, error: 'timeout' }); + }, 8000); + img.onload = function() { + if (!timedOut) { + clearTimeout(timer); + resolve({ time: performance.now() - start }); + } + }; + img.onerror = function() { + if (!timedOut) { + clearTimeout(timer); + /* Even 404 measures round-trip time */ + resolve({ time: performance.now() - start }); + } + }; + img.src = 'https://' + host + '/favicon.ico?_=' + Date.now() + '_' + Math.random().toString(36).slice(2,6); + }); + } + + function updateCardPing(card, time) { + var wrap = card.el.querySelector('.server-ping-wrap'); + if (time < 0) { + wrap.innerHTML = 'unreachable'; + } else { + var ms = time.toFixed(0); + var cls = 'done'; + if (time < 100) cls += ' fast'; + wrap.innerHTML = + '' + ms + '' + + 'ms'; + } + } + + function markBest() { + var valid = results.filter(function(r) { return r.time >= 0; }); + if (valid.length === 0) return; + valid.sort(function(a, b) { return a.time - b.time; }); + var best = valid[0]; + cards.forEach(function(c) { + if (c.server.host === best.host) { + c.el.classList.add('server-best'); + } + }); + /* Update banner status */ + bannerStatus.innerHTML = + ' All locations tested'; + /* Show optimal server */ + var bestCard = cards.filter(function(c) { return c.server.host === best.host; })[0]; + if (bestCard) { + setTimeout(function() { + optimalEl.innerHTML = + '
' + + ' ' + + 'Your optimal server location: ' + bestCard.server.city + ' (' + best.time.toFixed(0) + ' ms)' + + '
'; + optimalEl.classList.add('visible'); + }, 600); + } + } + + /* Ping all servers in parallel */ + cards.forEach(function(c) { + var idx = c.idx; + pingServer(c.server.host).then(function(result) { + result.host = c.server.host; + result.idx = idx; + results.push(result); + updateCardPing(c, result.time); + pending--; + if (pending === 0) { + markBest(); + } + }); + }); + })(); + fetch("blog/index.json?_=" + Date.now()) .then(function (r) { return r.json(); diff --git a/style.css b/style.css index e318c88..c194704 100644 --- a/style.css +++ b/style.css @@ -156,64 +156,228 @@ img { max-width: 100%; height: auto; vertical-align: middle; } .hero-stat i { color: var(--brass); font-size: 0.75rem; } .hero-actions { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem; } -/* ---- City Placards ---- */ -.city-placards { - display: flex; - gap: 0.75rem; - padding: 0 1.25rem; - margin: 0 auto; - max-width: 1200px; - width: 86%; - overflow-x: auto; - padding-bottom: 0.5rem; - position: relative; - z-index: 1; -} -.city-placard { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.3rem; - text-decoration: none; - flex-shrink: 0; - width: 8rem; - padding: 0.75rem 0.5rem 0.5rem; +/* ---- Server Status Banner ---- */ +.server-banner { + background: var(--midnight); + padding: 2rem 0 1.5rem; border-top: 1px solid var(--line-brass); - transition: all 0.25s; - border-radius: 0 0 var(--radius) var(--radius); + border-bottom: 1px solid var(--line-brass); + position: relative; + overflow: hidden; } -.city-placard:hover { - background: rgba(201,162,39,0.08); - border-top-color: var(--brass); +.server-banner::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(ellipse 60% 80% at 50% 100%, rgba(201,162,39,0.06) 0%, transparent 70%); + pointer-events: none; } -.city-placard img { - width: 3.5rem; - height: 3.5rem; - border-radius: 50%; - object-fit: cover; - border: 2px solid var(--line-brass); - transition: border-color 0.25s; +.server-banner-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.25rem; + flex-wrap: wrap; + gap: 0.5rem; } -.city-placard:hover img { border-color: var(--brass); } -.city-placard .city-name { +.server-banner-kicker { font-family: Marcellus, Georgia, serif; - font-size: 0.85rem; - color: #fff; - line-height: 1.2; -} -.city-placard .city-region { - font-family: Inter, sans-serif; - font-size: 0.62rem; - letter-spacing: 0.06em; + font-size: 1rem; + letter-spacing: 0.08em; text-transform: uppercase; - color: rgba(255,255,255,0.5); + color: var(--brass); +} +.server-banner-status { + font-family: Inter, sans-serif; + font-size: 0.78rem; + color: rgba(255,255,255,0.55); + display: flex; + align-items: center; + gap: 0.4rem; +} +.status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--brass); + display: inline-block; + animation: statusPulse 1.2s ease-in-out infinite; +} +@keyframes statusPulse { + 0%, 100% { opacity: 0.3; transform: scale(0.8); } + 50% { opacity: 1; transform: scale(1.2); } +} +.server-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 0.75rem; +} +.server-card { + background: rgba(255,255,255,0.035); + border: 1px solid rgba(201,162,39,0.12); + border-radius: var(--radius); + padding: 1rem 0.75rem; + text-align: center; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + position: relative; + opacity: 0; + transform: translateY(12px); +} +.server-card.visible { + opacity: 1; + transform: translateY(0); +} +.server-card:hover { + background: rgba(255,255,255,0.06); + border-color: rgba(201,162,39,0.25); +} +.server-card .server-city { + font-family: Marcellus, Georgia, serif; + font-size: 1.05rem; + color: #fff; + margin-bottom: 0.15rem; +} +.server-card .server-region { + font-family: Inter, sans-serif; + font-size: 0.68rem; + color: rgba(255,255,255,0.45); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 0.6rem; +} +.server-card .server-ping-wrap { + display: flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + min-height: 1.6rem; +} +.server-card .server-ping-icon { + font-size: 0.65rem; + color: rgba(255,255,255,0.2); +} +.server-card .server-ping-ms { + font-family: Inter, sans-serif; + font-size: 1.15rem; + font-weight: 600; + color: rgba(255,255,255,0.3); + letter-spacing: -0.02em; + transition: color 0.5s ease; +} +.server-card .server-ping-ms.done { + color: #fff; +} +.server-card .server-ping-ms.fast { + color: #4ade80; +} +.server-card .server-ping-unit { + font-size: 0.7rem; + color: rgba(255,255,255,0.3); + font-weight: 400; +} +.server-card .ping-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(201,162,39,0.15); + border-top-color: var(--brass); + border-radius: 50%; + animation: pingSpin 0.7s linear infinite; +} +@keyframes pingSpin { + to { transform: rotate(360deg); } +} +.server-card .server-ping-label { + font-size: 0.62rem; + color: rgba(255,255,255,0.25); + text-transform: uppercase; + letter-spacing: 0.04em; +} +.server-card.server-best { + background: rgba(201,162,39,0.1); + border-color: var(--brass); + box-shadow: 0 0 25px rgba(201,162,39,0.15), inset 0 0 25px rgba(201,162,39,0.04); + transform: translateY(-3px); +} +.server-card.server-best .server-city { + color: var(--brass-bright); +} +.server-card.server-best .server-ping-ms.fast { + color: var(--brass-bright); +} +.server-card .best-badge { + position: absolute; + top: -0.5rem; + right: -0.5rem; + background: var(--brass); + color: var(--midnight); + font-size: 0.55rem; + font-weight: 700; + font-family: Inter, sans-serif; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 0.2rem 0.5rem; + border-radius: var(--radius-pill); + opacity: 0; + transform: scale(0.5); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + pointer-events: none; +} +.server-card.server-best .best-badge { + opacity: 1; + transform: scale(1); +} +.server-optimal { + text-align: center; + margin-top: 1rem; + min-height: 2rem; + opacity: 0; + transform: translateY(10px); + transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1); +} +.server-optimal.visible { + opacity: 1; + transform: translateY(0); +} +.server-optimal-inner { + display: inline-block; + font-family: Marcellus, Georgia, serif; + font-size: 1rem; + color: rgba(255,255,255,0.6); + letter-spacing: 0.02em; + border-top: 1px solid rgba(201,162,39,0.2); + padding-top: 0.75rem; +} +.server-optimal-inner strong { + color: var(--brass-bright); + font-weight: 400; +} +.server-optimal-inner .optimal-arrow { + display: inline-block; + margin: 0 0.3rem; + animation: optimalArrowBounce 1.6s ease-in-out infinite; +} +@keyframes optimalArrowBounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-3px); } +} + +@media (max-width: 992px) { + .server-grid { + grid-template-columns: repeat(3, 1fr); + } +} +@media (max-width: 576px) { + .server-grid { + grid-template-columns: repeat(2, 1fr); + } + .server-banner-header { + flex-direction: column; + align-items: flex-start; + } } @media (max-width: 768px) { .hero { min-height: auto; padding: 3rem 0 0; } - .city-placards { width: 100%; padding: 0 1rem 0.5rem; gap: 0.5rem; } - .city-placard { width: 6rem; padding: 0.5rem 0.25rem; } - .city-placard img { width: 2.8rem; height: 2.8rem; } .hero-actions { flex-direction: column; } .hero-actions .btn { width: 100%; text-align: center; } .hero-stats { gap: 0.75rem; }