// direction-a-fieldmode.jsx — "On the job" / Field Mode section.
// Synced to CURRENT project_blackdevil source (July 2026):
// Screen 1 (Arrival) ← src/components/arrived-flow/ArrivalScreen.tsx
// Screen 2 (In Progress)← src/components/arrived-flow/InProgressScreen.tsx
// NOTE: this screen has NO voice/mic hero — it's a plain timer + photo
// tags + notes textarea + materials checklist. The old mockup showed a
// "Live Job" voice cockpit that TodaySurface.tsx's own comments mark as
// the legacy "v1 Open Live Job" — a separate, still-reachable but
// unconverted door, not what starting a job from Arrival leads to. This
// rebuild matches the screen the primary Start-Job path actually shows.
// Screens 3/4 (Wrap-up/Complete) ← src/components/JobCompletionSheet.tsx
// (confirmed real: hours/materials review, "While you were there"
// cross-sell, send/cash/draft payment chooser — exact copy for the
// completion-tag chips wasn't verifiable in source, so that part is
// generalized rather than invented).
// None of these three files use Shift Board tokens yet — they're still on
// the older dark palette (ArrivalScreen/InProgressScreen: pre-Rams "Refined
// Industrial" tokens; JobCompletionSheet: Rams). All now repaint with the
// July 2 brand-color unification (--brand / --brand-rams → new orange).
// Field-mode palette — resolved 1:1 from arrived-flow's REAL tokens
// (arrived-flow/types.ts → globals.css :root, the pre-Rams "Refined
// Industrial" block that ArrivalScreen/InProgressScreen still use).
const FM = {
bg: '#0a0a0b', // --app-bg
raised: '#121110', // --surface-raised (darker/plainer than the warm Rams #22201c)
raised2: '#1a1a1a', // --surface-overlay
border: '#272727', // --border-default
borderSubtle: 'rgba(255,255,255,0.1)', // --border-subtle
brand: '#ff5c00', // --brand (July 2 unification)
brandDark: '#d35400',
brandMuted: 'rgba(255,92,0,0.12)',
brandBorder: 'rgba(255,92,0,0.3)',
success: '#10b981', // --success
successDark: '#059669',
successMuted: 'rgba(16,185,129,0.12)',
successBorder: 'rgba(16,185,129,0.2)',
danger: '#ef4444',
warn: '#fbbf24',
blue: '#06B6D4', // On My Way / Send ETA — hardcoded in ArrivalScreen
blueMuted: 'rgba(6,182,212,0.10)',
blueBorder: 'rgba(6,182,212,0.25)',
text: '#ffffff', // --text-primary
text2: '#c2c8d1', // --text-secondary
text3: '#9ca3af', // --text-tertiary
font: "'Inter', -apple-system, system-ui, sans-serif",
};
function FMStatusBar() {
return (
{/* Send ETA & Navigate — cyan choreographed text-then-map action,
distinct from the quick actions above (confirmed real, was
missing from earlier mockups). */}
Send ETA (12 min) & Navigate
{/* Bottom CTA */}
Start Job Timer
);
}
// ─── Screen 2: In Progress (InProgressScreen.tsx) ───
// The REAL screen: timer header w/ progress bar, tagged photo grid, a plain
// notes textarea, and a materials checklist — no voice assistant.
function FMInProgress() {
const photos = [
{ label: 'Before', has: true },
{ label: 'During', has: false },
];
const materials = [
{ name: 'Smart thermostat (Ecobee)', cost: '$185.00', preloaded: true, used: true },
{ name: '18-gauge thermostat wire, 25ft', cost: '$12.50', preloaded: false },
];
return (
{/* Timer header */}
In Progress
AC condenser — not cooling
34:12
Est. 2 hrs
{/* Scrollable content */}
{/* Job Photos */}
Job Photos
1 captured
Before
Add
{/* Job Notes */}
Job Notes
Capacitor read low on arrival — replacing 45/5 MFD dual-run. Customer mentioned a burning smell last week.
{/* Materials Used */}
Materials Used
{materials.map((m, i) => (
{m.name}
{m.cost}
{m.preloaded && (
Pre-loaded
)}
))}
Add material
{/* Bottom CTA */}
Found Additional Work
Complete Job
);
}
// Shared dimmed backdrop + bottom-sheet shell for the completion screens.
function FMSheetBackdrop() {
return (
);
}
// ─── Screen 3: Wrap-up (JobCompletionSheet.tsx, review step) ───
// Confirmed real: hours + materials review, and the "While you were there"
// cross-sell upsell (verbatim casing/heading from source). The exact
// completion-tag UI wasn't verifiable in source, so this leads with what is.
function FMWrapup() {
const upsells = [
['Hard-start kit', 'Extends compressor life on older units'],
['Replace the contactor', 'Common next failure — cheap to do while it’s open'],
];
return (
);
}
const FM_STEPS = [
{ key: 'arrival', label: 'Arrive', title: 'You pull up. Werkd already knows the job.',
body: 'GPS flips the screen to this job the second you arrive — customer history, the quote, and gate codes, all there. One tap starts the timer.',
render: () => },
{ key: 'inprogress', label: 'Work', title: 'Photos, notes, materials — as you go.',
body: 'The timer runs while you work. Tag before/after photos, jot a note, check off materials as you use them — everything lands on the invoice automatically.',
render: () => },
{ key: 'wrapup', label: 'Wrap up', title: 'Werkd spots the work you’d have driven past.',
body: 'Review the hours and materials it tracked, then "While you were there" surfaces the easy add-ons for THIS job — found money most apps never mention.',
render: () => },
{ key: 'complete', label: 'Get paid', title: 'Job done. Invoice already built.',
body: 'Tap complete and the invoice is ready to review. Email it, text a payment link, or mark it paid in cash — before you leave the driveway.',
render: () => },
];
function AFieldMode() {
const DA = window.DA;
const m = window.useIsMobile();
const [i, setI] = React.useState(0);
const [paused, setPaused] = React.useState(false);
const [typed, setTyped] = React.useState('');
// Type the body out (~18ms/char) — reads like Werkd's AI narrating the job —
// then dwell ~2.6s and advance. Paused shows the full text and halts advance.
React.useEffect(() => {
const full = FM_STEPS[i].body;
if (paused) { setTyped(full); return; }
setTyped('');
let idx = 0;
const SPEED = 18;
const typeId = setInterval(() => {
idx += 1;
setTyped(full.slice(0, idx));
if (idx >= full.length) clearInterval(typeId);
}, SPEED);
const advanceId = setTimeout(() => setI((p) => (p + 1) % FM_STEPS.length), full.length * SPEED + 6200);
return () => { clearInterval(typeId); clearTimeout(advanceId); };
}, [i, paused]);
const step = FM_STEPS[i];
// Derive the active index from `step` itself (which provably tracks the
// phone + caption) so the rail/progress can't desync from them.
const fmActive = FM_STEPS.indexOf(step);
return (
FIELD MODE
The whole job, on site
GPS knows the moment you pull up, and werkd takes over the whole visit — one full-screen flow from the driveway to paid. No hunting through menus with dirty hands.
{/* phone */}
{step.render()}
{/* caption + rail */}
{/* step rail */}
{FM_STEPS.map((s, idx) => (
))}
{step.title}
{/* Body types out; a hidden full-text sizer reserves the height so
nothing below jumps as lines fill in. minHeight reserves the
tallest stage's body so the centered column never re-centers. */}