# Science Calculators

`@indoku/science` is a pure TypeScript engine with no dependencies and no framework: physics, chemistry, math and Cartesian helpers you can call from a calculator, a server route or a React component.

These demos are plain inputs wired to engine functions. Wrap the results in `FormulaBlock`, `Graph2D` or `Plot3D` for a richer UI.

Group: Science

## Import

```tsx
import { waveSpeed, molarMass, balanceEquation, projectile } from "@indoku/science"

// Subpaths keep bundles small:
import { molarMass } from "@indoku/science/chemistry"
```

## Examples

### Waves

Wave speed and the Doppler effect.

```tsx
function CalcWave() {
  const [f, setF] = useState("440")
  const [l, setL] = useState("0.78")
  const [vs, setVs] = useState("20")
  return (
    <div style={box}>
      <div style={row}>
        <label>f (Hz) <input value={f} onChange={(e) => setF(e.target.value)} size={6} /></label>
        <label>λ (m) <input value={l} onChange={(e) => setL(e.target.value)} size={6} /></label>
      </div>
      <div>v = fλ = <b>{waveSpeed(num(f), num(l)).toFixed(2)}</b> m/s</div>
      <label>Source speed toward you (m/s) <input value={vs} onChange={(e) => setVs(e.target.value)} size={4} /></label>
      <div>Doppler: <b>{dopplerFrequency(num(f), 0, num(vs)).toFixed(1)}</b> Hz</div>
    </div>
  )
}
```

### Fluids

Reynolds number and flow regime.

```tsx
function CalcFluid() {
  const [v, setV] = useState("1.5")
  const [d, setD] = useState("0.05")
  const re = reynoldsNumber(1000, num(v), num(d), 0.001)
  return (
    <div style={box}>
      <div style={row}>
        <label>v (m/s) <input value={v} onChange={(e) => setV(e.target.value)} size={6} /></label>
        <label>D (m) <input value={d} onChange={(e) => setD(e.target.value)} size={6} /></label>
      </div>
      <div>Water, Re = <b>{Math.round(re)}</b> → {flowRegime(re)}</div>
    </div>
  )
}
```

### Projectile

```tsx
function CalcProjectile() {
  const [speed, setSpeed] = useState("20")
  const [angle, setAngle] = useState("45")
  const p = projectile({ speed: num(speed), angleDeg: num(angle) })
  return (
    <div style={box}>
      <div style={row}>
        <label>v₀ (m/s) <input value={speed} onChange={(e) => setSpeed(e.target.value)} size={5} /></label>
        <label>θ (°) <input value={angle} onChange={(e) => setAngle(e.target.value)} size={5} /></label>
      </div>
      <div>Range <b>{p.range.toFixed(2)}</b> m · max height <b>{p.maxHeight.toFixed(2)}</b> m · time <b>{p.flightTime.toFixed(2)}</b> s</div>
    </div>
  )
}
```

### Molar mass

Parses brackets and hydrates such as `CuSO4·5H2O`.

```tsx
function CalcMolarMass() {
  const [formula, setFormula] = useState("Ca(OH)2")
  let out: { mass: number; pct: Record<string, number> } | null = null
  let error = ""
  try {
    out = { mass: molarMass(formula), pct: percentComposition(formula) }
  } catch (e) {
    error = (e as Error).message
  }
  return (
    <div style={box}>
      <input value={formula} onChange={(e) => setFormula(e.target.value)} style={{ fontFamily: "monospace" }} />
      {error && <small style={{ color: "crimson" }}>{error}</small>}
      {out && (
        <div>
          <b>{out.mass.toFixed(3)}</b> g/mol · {Object.entries(out.pct).map(([k, v]) => `${k} ${v.toFixed(1)}%`).join(" · ")}
        </div>
      )}
    </div>
  )
}
```

### Equation balancer

```tsx
function CalcBalance() {
  const [eq, setEq] = useState("C3H8 + O2 -> CO2 + H2O")
  let result = ""
  try {
    result = balanceEquation(eq).equation
  } catch (e) {
    result = (e as Error).message
  }
  return (
    <div style={box}>
      <input value={eq} onChange={(e) => setEq(e.target.value)} style={{ fontFamily: "monospace" }} />
      <div><b>{result}</b></div>
    </div>
  )
}
```

### pH

Strong acid and weak acid (exact quadratic).

```tsx
function CalcPH() {
  const [c, setC] = useState("0.01")
  const [ka, setKa] = useState("1.8e-5")
  return (
    <div style={box}>
      <div style={row}>
        <label>Concentration (M) <input value={c} onChange={(e) => setC(e.target.value)} size={7} /></label>
        <label>Ka <input value={ka} onChange={(e) => setKa(e.target.value)} size={7} /></label>
      </div>
      <div>Strong acid: pH <b>{pHStrongAcid(num(c)).toFixed(2)}</b> · weak acid: pH <b>{pHWeakAcid(num(ka), num(c)).toFixed(2)}</b></div>
    </div>
  )
}
```

### Quadratic equation

Complex roots included.

```tsx
function CalcQuadratic() {
  const [a, setA] = useState("1")
  const [b, setB] = useState("-3")
  const [c, setC] = useState("2")
  let text = ""
  try {
    const r = solveQuadratic(num(a), num(b), num(c))
    text = r.roots.map((x) => (x.im === 0 ? x.re.toFixed(3) : `${x.re.toFixed(3)} ${x.im < 0 ? "−" : "+"} ${Math.abs(x.im).toFixed(3)}i`)).join(",  ")
  } catch (e) {
    text = (e as Error).message
  }
  return (
    <div style={box}>
      <div style={row}>
        <label>a <input value={a} onChange={(e) => setA(e.target.value)} size={4} /></label>
        <label>b <input value={b} onChange={(e) => setB(e.target.value)} size={4} /></label>
        <label>c <input value={c} onChange={(e) => setC(e.target.value)} size={4} /></label>
      </div>
      <div>x = <b>{text}</b></div>
    </div>
  )
}
```

## Notes

- All functions use SI units unless the name says otherwise.
- Invalid input throws a plain `Error`; catch it and show the message.

## See also

- Graph 2D: https://ui.indoku.org/docs/graph-2d
- Periodic Table: https://ui.indoku.org/docs/periodic-table
- Math: https://ui.indoku.org/docs/math
