import React, { useMemo, useState } from "react"; /* ------------------------------------------------------------------ */ /* DIP PROJECT — formation depth step-out calculator */ /* Projects a known formation TVD along a bearing using regional dip. */ /* Seeded with the Iron Horse / Mid Bossier example. */ /* ------------------------------------------------------------------ */ const C = { bg: "#0E1A22", panel: "#16242E", panel2: "#1B2D38", line: "#26404D", text: "#E8EEF1", muted: "#8AA0AD", amber: "#F2B441", amberDim: "#B8842B", coral: "#E2674A", band: "#22414F", }; const SANS = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"; const MONO = "'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace"; const toRad = (d) => (d * Math.PI) / 180; const num = (v, d = 0) => { const n = parseFloat(v); return Number.isFinite(n) ? n : d; }; const fmt = (n, dp = 0) => Number.isFinite(n) ? n.toLocaleString("en-US", { minimumFractionDigits: dp, maximumFractionDigits: dp }) : "—"; function niceStep(range, target) { if (!(range > 0)) return 1; const raw = range / target; const mag = Math.pow(10, Math.floor(Math.log10(raw))); const norm = raw / mag; let s = 10; if (norm < 1.5) s = 1; else if (norm < 3) s = 2; else if (norm < 7) s = 5; return s * mag; } const COMPASS = [ ["N", 0], ["NE", 45], ["E", 90], ["SE", 135], ["S", 180], ["SW", 225], ["W", 270], ["NW", 315], ]; export default function DipProjector() { const [mode, setMode] = useState("distance"); // 'distance' | 'depth' const [refTVD, setRefTVD] = useState("13500"); const [targetTVD, setTargetTVD] = useState("19000"); const [distMi, setDistMi] = useState("15"); const [dip, setDip] = useState(300); // ft / mile (true dip) const [stepBear, setStepBear] = useState(180); const [dipAz, setDipAz] = useState(180); const [ppGrad, setPpGrad] = useState("0.90"); const [geoGrad, setGeoGrad] = useState("1.5"); // °F per 100 ft const [surfTemp, setSurfTemp] = useState("74"); const calc = useMemo(() => { const rTVD = num(refTVD); const tTVD = num(targetTVD); const d = Math.max(0, num(distMi)); const off = stepBear - dipAz; const cosOff = Math.cos(toRad(off)); const dApp = dip * cosOff; // apparent dip ft/mile along bearing let solvedMi = NaN; let note = null; let markerDist = NaN; let markerDepth = NaN; let reachable = false; if (mode === "distance") { const delta = tTVD - rTVD; if (delta <= 0) { note = "Target is at or above the reference depth — no down-dip step-out needed."; } else if (dApp <= 1e-6) { note = off === 0 ? "Dip is zero — the formation is flat, target depth is never reached." : "This bearing runs up-dip or along strike, so it gets shallower — never reaches the target."; } else { solvedMi = delta / dApp; reachable = true; markerDist = solvedMi; markerDepth = tTVD; } } else { markerDist = d; markerDepth = rTVD + d * dApp; reachable = true; } const readoutDepth = mode === "distance" ? tTVD : Number.isFinite(markerDepth) ? markerDepth : rTVD; const pp = num(ppGrad) * readoutDepth; const emw = num(ppGrad) / 0.052; const bht = num(surfTemp) + (num(geoGrad) / 100) * readoutDepth; return { rTVD, tTVD, d, dApp, cosOff, off, solvedMi, note, markerDist, markerDepth, reachable, readoutDepth, pp, emw, bht, }; }, [mode, refTVD, targetTVD, distMi, dip, stepBear, dipAz, ppGrad, geoGrad, surfTemp]); /* ---------- cross-section geometry ---------- */ const PX_L = 64, PX_R = 700, PX_T = 26, PX_B = 402; const chart = useMemo(() => { const { rTVD, dApp, markerDist, markerDepth, reachable, mode: m } = calc; let base = m === "distance" ? reachable ? calc.solvedMi : 10 : Math.max(calc.d, 1); if (!Number.isFinite(base) || base <= 0) base = 10; const Dmax = Math.min(Math.max(base * 1.3, 2), 120); const deepAtDmax = rTVD + Dmax * dApp; const depths = [rTVD, deepAtDmax]; if (Number.isFinite(markerDepth)) depths.push(markerDepth); let dLo = Math.min(...depths) - 250; let dHi = Math.max(...depths) + 350; if (dHi - dLo < 800) dHi = dLo + 800; const sx = (mi) => PX_L + (mi / Dmax) * (PX_R - PX_L); const sy = (ft) => PX_T + ((ft - dLo) / (dHi - dLo)) * (PX_B - PX_T); const dStep = niceStep(dHi - dLo, 5); const depthTicks = []; for (let v = Math.ceil(dLo / dStep) * dStep; v <= dHi; v += dStep) depthTicks.push(v); const xStep = niceStep(Dmax, 5); const xTicks = []; for (let v = 0; v <= Dmax + 1e-6; v += xStep) xTicks.push(v); return { Dmax, deepAtDmax, dLo, dHi, sx, sy, depthTicks, xTicks }; }, [calc]); const inputStyle = { width: "100%", background: C.bg, border: `1px solid ${C.line}`, color: C.text, fontFamily: MONO, fontSize: 15, padding: "9px 11px", borderRadius: 6, outline: "none", boxSizing: "border-box", }; const labelStyle = { fontFamily: SANS, fontSize: 10.5, letterSpacing: "0.13em", textTransform: "uppercase", color: C.muted, marginBottom: 6, display: "block", fontWeight: 600, }; const Field = ({ label, value, onChange, suffix }) => (