mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(dashboard): retire the 4.0.0 moment after Aug 23 — viewable-until badge + a permanent dismiss X
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
import { lazy, Suspense, useEffect, useRef, useState } from 'react'
|
||||
import { Play } from 'lucide-react'
|
||||
import { Play, X } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { noFearCopy } from './noFearLines'
|
||||
import { noFearChrome, noFearCopy } from './noFearLines'
|
||||
import './fourzero.css'
|
||||
|
||||
// TREK 4.0.0 release moment — remove together with the FourZero folder.
|
||||
|
||||
const NoFearShow = lazy(() => import('./NoFearShow'))
|
||||
|
||||
// The moment retires itself. Last day it shows: 2026-08-23 (local midnight after
|
||||
// it ends the run). After that the beacon renders nothing even if the folder is
|
||||
// still in the build — so self-hosters who don't update past this version still
|
||||
// see it disappear on time. The end date holds regardless of updates.
|
||||
const VISIBLE_UNTIL = new Date('2026-08-24T00:00:00').getTime()
|
||||
const DISMISS_KEY = 'trek.fourzero.dismissed'
|
||||
|
||||
// Abstract night-world for the teaser: light points loosely shaped like the
|
||||
// inhabited continents (relative coords), between which golden arcs travel.
|
||||
const TEASER_POINTS: [number, number][] = [
|
||||
@@ -123,7 +130,20 @@ function TeaserCanvas() {
|
||||
export default function NoFearBeacon() {
|
||||
const { language } = useTranslation()
|
||||
const copy = noFearCopy(language)
|
||||
const chrome = noFearChrome(language)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [confirming, setConfirming] = useState(false)
|
||||
const [dismissed, setDismissed] = useState(() => {
|
||||
try { return localStorage.getItem(DISMISS_KEY) === '1' } catch { return false }
|
||||
})
|
||||
|
||||
// Past the end date, or dismissed for good — nothing renders.
|
||||
if (dismissed || Date.now() >= VISIBLE_UNTIL) return null
|
||||
|
||||
const dismiss = () => {
|
||||
try { localStorage.setItem(DISMISS_KEY, '1') } catch { /* private mode — hide for this session at least */ }
|
||||
setDismissed(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -135,6 +155,21 @@ export default function NoFearBeacon() {
|
||||
<span className="fz-beacon-sub" style={{ color: 'rgba(245, 240, 225, 0.75)' }}>{copy.beaconSub}</span>
|
||||
<span className="fz-beacon-playbtn" aria-hidden><Play size={16} fill="currentColor" /></span>
|
||||
</button>
|
||||
{/* Top-right X: two-step so a stray click can't retire the moment for good.
|
||||
Dismissing is permanent (localStorage) — only an update brings it back. */}
|
||||
<button
|
||||
type="button"
|
||||
className={`fz-beacon-dismiss ${confirming ? 'fz-beacon-dismiss-confirm' : ''}`}
|
||||
aria-label={chrome.dismiss}
|
||||
title={chrome.dismiss}
|
||||
onClick={() => (confirming ? dismiss() : setConfirming(true))}
|
||||
onMouseLeave={() => setConfirming(false)}
|
||||
onBlur={() => setConfirming(false)}
|
||||
>
|
||||
{confirming ? chrome.confirm : <X size={15} />}
|
||||
</button>
|
||||
{/* White retirement badge: viewable until Aug 23. */}
|
||||
<span className="fz-beacon-until">{chrome.until}</span>
|
||||
</div>
|
||||
{open && (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -396,19 +396,59 @@
|
||||
|
||||
.fz-beacon-dismiss {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
inset-inline-start: 8px;
|
||||
z-index: 2;
|
||||
top: 10px;
|
||||
inset-inline-end: 10px;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: rgba(245, 240, 225, 0.4);
|
||||
background: rgba(8, 10, 16, 0.4);
|
||||
color: rgba(245, 240, 225, 0.55);
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(4px);
|
||||
transition: opacity 0.3s ease, color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.fz-beacon-dismiss:hover { color: rgba(245, 240, 225, 0.85); }
|
||||
/* Stays out of the way until the card is hovered or the button is focused. */
|
||||
.fz-beacon:hover .fz-beacon-dismiss,
|
||||
.fz-beacon-dismiss:focus-visible { opacity: 1; }
|
||||
|
||||
.fz-beacon-dismiss:hover { color: #fff; background: rgba(8, 10, 16, 0.65); }
|
||||
|
||||
/* Armed confirm: a clear, deliberate red pill — this click is permanent. */
|
||||
.fz-beacon-dismiss-confirm {
|
||||
opacity: 1;
|
||||
padding: 5px 11px;
|
||||
background: rgba(200, 60, 45, 0.9);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fz-beacon-dismiss-confirm:hover { background: rgba(214, 68, 52, 0.95); }
|
||||
|
||||
/* White retirement badge at the foot of the card: viewable until Aug 23. */
|
||||
.fz-beacon-until {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 14px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: #1a1712;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* While the show runs: no floating dashboard chrome above it, no page scroll. */
|
||||
body.fz-show-open { overflow: hidden; }
|
||||
|
||||
@@ -789,3 +789,45 @@ export const ANTHEM_CASCADE: { lang: string; text: string }[] = [
|
||||
]
|
||||
|
||||
export const INSPIRED_URL = 'https://www.youtube.com/watch?v=Gg-SCpXba64'
|
||||
|
||||
// The beacon retires itself after the release window. The white "viewable until"
|
||||
// badge and the dismiss copy live in one compact table so the 23 language blocks
|
||||
// above stay untouched — and it all still leaves with the folder.
|
||||
interface NoFearChrome {
|
||||
/** The white badge: this moment is viewable until Aug 23. */
|
||||
until: string
|
||||
/** aria-label / title of the top-right X. */
|
||||
dismiss: string
|
||||
/** Armed confirm label — a stray click must not retire it for good. */
|
||||
confirm: string
|
||||
}
|
||||
|
||||
const CHROME: Record<string, NoFearChrome> = {
|
||||
de: { until: 'Noch bis 23.08. zu sehen', dismiss: 'Ausblenden', confirm: 'Dauerhaft ausblenden?' },
|
||||
en: { until: 'Shown until Aug 23', dismiss: 'Dismiss', confirm: 'Hide for good?' },
|
||||
es: { until: 'Visible hasta el 23 ago', dismiss: 'Ocultar', confirm: '¿Ocultar para siempre?' },
|
||||
fr: { until: 'Visible jusqu’au 23 août', dismiss: 'Masquer', confirm: 'Masquer définitivement ?' },
|
||||
it: { until: 'Visibile fino al 23 ago', dismiss: 'Nascondi', confirm: 'Nascondere per sempre?' },
|
||||
nl: { until: 'Te zien tot 23 aug', dismiss: 'Verbergen', confirm: 'Voorgoed verbergen?' },
|
||||
pl: { until: 'Widoczne do 23 sierpnia', dismiss: 'Ukryj', confirm: 'Ukryć na zawsze?' },
|
||||
ru: { until: 'Показывается до 23 августа', dismiss: 'Скрыть', confirm: 'Скрыть навсегда?' },
|
||||
uk: { until: 'Доступно до 23 серпня', dismiss: 'Сховати', confirm: 'Сховати назавжди?' },
|
||||
cs: { until: 'K vidění do 23. srpna', dismiss: 'Skrýt', confirm: 'Skrýt natrvalo?' },
|
||||
sv: { until: 'Visas till 23 aug', dismiss: 'Dölj', confirm: 'Dölj för gott?' },
|
||||
tr: { until: '23 Ağustos’a kadar görünür', dismiss: 'Gizle', confirm: 'Kalıcı olarak gizlensin mi?' },
|
||||
gr: { until: 'Έως 23 Αυγούστου', dismiss: 'Απόκρυψη', confirm: 'Μόνιμη απόκρυψη;' },
|
||||
hu: { until: 'Augusztus 23-ig látható', dismiss: 'Elrejtés', confirm: 'Végleg elrejti?' },
|
||||
br: { until: 'Visível até 23 de ago', dismiss: 'Ocultar', confirm: 'Ocultar para sempre?' },
|
||||
ca: { until: 'Visible fins al 23 d’ag.', dismiss: 'Amaga', confirm: 'Amagar per sempre?' },
|
||||
id: { until: 'Tampil hingga 23 Agu', dismiss: 'Sembunyikan', confirm: 'Sembunyikan selamanya?' },
|
||||
vi: { until: 'Hiển thị đến 23 tháng 8', dismiss: 'Ẩn', confirm: 'Ẩn vĩnh viễn?' },
|
||||
ja: { until: '8月23日まで公開', dismiss: '非表示', confirm: '完全に非表示にしますか?' },
|
||||
ko: { until: '8월 23일까지 표시', dismiss: '숨기기', confirm: '영구히 숨기시겠어요?' },
|
||||
zh: { until: '展示至 8月23日', dismiss: '隐藏', confirm: '永久隐藏?' },
|
||||
ar: { until: 'يظهر حتى 23 أغسطس', dismiss: 'إخفاء', confirm: 'إخفاء نهائيًا؟' },
|
||||
'zh-TW': { until: '展示至 8月23日', dismiss: '隱藏', confirm: '永久隱藏?' },
|
||||
}
|
||||
|
||||
export function noFearChrome(language: string): NoFearChrome {
|
||||
return CHROME[language] ?? CHROME.en
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user