Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { IS_DEMO_MODE, seedDemoState } from './utils/demoSeed';
import OSShell from './components/layout/OSShell';
import GameEventOverlay from './components/layout/GameEventOverlay';
import RaidEventOverlay from './components/layout/RaidEventOverlay';
import GetBackClock from './components/common/GetBackClock';
import TutorialOverlay from './components/tutorial/TutorialOverlay';
import NPCThreatBanner from './components/map/NPCThreatBanner';

Expand Down Expand Up @@ -63,6 +64,7 @@ const WeeklyUpdateRoute = React.lazy(() => import('./components/news/WeeklyUpdat
const PhoneApp = React.lazy(() => import('./components/phone/PhoneApp'));
const AttackPlanner = React.lazy(() => import('./components/missions/AttackPlanner'));
const TrapApp = React.lazy(() => import('./components/trap/TrapApp'));
const MostWantedApp = React.lazy(() => import('./components/economy/MostWantedApp'));

import './App.css';

Expand Down Expand Up @@ -196,6 +198,7 @@ const App: React.FC = () => {
case 'news': return <Suspense fallback={<LazyFallback />}><WeeklyUpdateRoute key="news" /></Suspense>;
case 'phone': return <Suspense fallback={<LazyFallback />}><PhoneApp key="phone" /></Suspense>;
case 'trap': return <Suspense fallback={<LazyFallback />}><TrapApp key="trap" /></Suspense>;
case 'most_wanted': return <Suspense fallback={<LazyFallback />}><MostWantedApp key="most_wanted" /></Suspense>;
default:
return (
<OSShell
Expand Down Expand Up @@ -238,6 +241,9 @@ const App: React.FC = () => {
<NPCThreatBanner />
<TutorialOverlay />

{/* Get Back shot clock — global HUD. Hides itself when no debt is open. */}
<GetBackClock />

{/* Payroll modal — renders when Shoebox can't cover weekly wages */}
{salarySystem.payrollDue && <PayrollModal />}

Expand Down
281 changes: 281 additions & 0 deletions frontend/src/components/common/GetBackClock.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/* ============================================================
GetBackClock — revenge shot clock HUD
Sprint 15-B
============================================================ */

/* ── Badge ── */
.gbc-badge {
position: fixed;
left: 50%;
bottom: 92px;
transform: translateX(-50%);
z-index: 620;

display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px 10px;
border-radius: 999px;
border: 1px solid rgba(239, 68, 68, 0.45);
background: rgba(20, 8, 8, 0.93);
backdrop-filter: blur(8px);
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55);
cursor: pointer;
overflow: hidden;
}

.gbc-badge.urgent {
border-color: rgba(239, 68, 68, 0.85);
animation: gbc-pulse 1.4s ease-in-out infinite;
}

@keyframes gbc-pulse {
0%, 100% { box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55); }
50% { box-shadow: 0 4px 26px rgba(239, 68, 68, 0.55); }
}

.gbc-badge-label {
font-size: 9px;
font-weight: 800;
letter-spacing: 1.6px;
color: #fca5a5;
}

.gbc-badge-clock {
font-size: 15px;
font-weight: 900;
letter-spacing: 0.6px;
color: #fff;
/* Tabular figures keep the clock from jittering as digits change. */
font-variant-numeric: tabular-nums;
}

.gbc-badge.urgent .gbc-badge-clock {
color: #fca5a5;
}

.gbc-badge-more {
padding: 1px 6px;
border-radius: 999px;
background: rgba(239, 68, 68, 0.28);
color: #fecaca;
font-size: 9px;
font-weight: 800;
}

.gbc-badge-bar {
position: absolute;
left: 0;
bottom: 0;
height: 3px;
background: linear-gradient(90deg, #ef4444, #f59e0b);
transition: width 1s linear;
}

/* ── Overlay panel ── */
.gbc-overlay {
position: fixed;
inset: 0;
z-index: 940;
display: flex;
align-items: flex-end;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
}

.gbc-panel {
width: 100%;
max-width: 440px;
max-height: 88vh;
overflow-y: auto;
padding: 18px 16px 22px;
border-radius: 16px 16px 0 0;
background: linear-gradient(180deg, #1a0d0d, #0a0708);
border-top: 1px solid rgba(239, 68, 68, 0.3);
}

.gbc-panel-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 8px;
}

.gbc-panel-title {
font-size: 16px;
font-weight: 900;
letter-spacing: 2.4px;
color: #fca5a5;
}

.gbc-panel-sub {
font-size: 10px;
color: #78716c;
}

.gbc-explainer {
margin: 0 0 14px;
font-size: 11px;
color: #9ca3af;
line-height: 1.6;
}

/* ── Items ── */
.gbc-list {
display: flex;
flex-direction: column;
gap: 11px;
}

.gbc-item {
padding: 12px;
border-radius: 10px
;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
}

.gbc-item.urgent {
border-color: rgba(239, 68, 68, 0.45);
background: rgba(239, 68, 68, 0.07);
}

.gbc-item-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 7px;
}

.gbc-item-trigger {
font-size: 9px;
font-weight: 800;
letter-spacing: 1.1px;
color: #a8a29e;
}

.gbc-item-clock {
font-size: 17px;
font-weight: 900;
color: #e7e5e4;
font-variant-numeric: tabular-nums;
}

.gbc-item-clock.urgent {
color: #f87171;
}

.gbc-item-track {
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
overflow: hidden;
margin-bottom: 9px;
}

.gbc-item-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #4ade80, #facc15);
transition: width 1s linear;
}

.gbc-item-fill.urgent {
background: linear-gradient(90deg, #ef4444, #f97316);
}

.gbc-item-lost {
font-size: 11px;
color: #a1a1aa;
margin-bottom: 5px;
}

.gbc-item-lost strong {
color: #f4f4f5;
font-weight: 700;
}

.gbc-item-copy {
margin: 0 0 9px;
font-size: 11px;
color: #78716c;
line-height: 1.55;
}

/* ── Wanted list ── */
.gbc-wanted {
margin-bottom: 9px;
}

.gbc-wanted-label {
display: block;
font-size: 8px;
font-weight: 800;
letter-spacing: 1.2px;
color: #52525b;
margin-bottom: 5px;
}

.gbc-wanted-names {
display: flex;
flex-wrap: wrap;
gap: 5px;
}

.gbc-wanted-name {
padding: 3px 8px;
border-radius: 999px;
background: rgba(239, 68, 68, 0.14);
border: 1px solid rgba(239, 68, 68, 0.28);
color: #fca5a5;
font-size: 9px;
font-weight: 700;
}

/* ── Stakes ── */
.gbc-stakes {
display: flex;
justify-content: space-between;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
font-size: 10px;
font-weight: 700;
color: #4ade80;
}

.gbc-stakes-loss {
color: #f87171;
}

/* ── Actions ── */
.gbc-hunt-btn {
width: 100%;
margin-top: 16px;
padding: 13px;
border: none;
border-radius: 9px;
background: linear-gradient(135deg, #dc2626, #991b1b);
color: #fff;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.1px;
cursor: pointer;
}

.gbc-hunt-btn:active {
transform: scale(0.98);
}

.gbc-close {
width: 100%;
margin-top: 8px;
padding: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: transparent;
color: #71717a;
font-size: 11px;
cursor: pointer;
}
Loading
Loading