lift-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev f9f4e37d6945
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 23e397d8a5d5bf7756362640
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/Plates.tsx
// Plate calculator: which plates go on each side of the bar for a target weight.
import { useState } from "react";
import { parseNum, showW, type Unit } from "./lib/model";
import { useStore } from "./lib/store";

const PLATES: Record<Unit, number[]> = { kg: [25, 20, 15, 10, 5, 2.5, 1.25], lb: [45, 35, 25, 10, 5, 2.5] };
const BARS: Record<Unit, number[]> = { kg: [20, 15, 10, 0], lb: [45, 35, 25, 0] };
// More than any bar holds. Without a cap a link like /plates?w=1e308 never stops adding plates.
const MAX_LOAD: Record<Unit, number> = { kg: 1000, lb: 2200 };

/** The heaviest plates first on each side, as close to the target as the plates allow without going over. */
export function platesFor(target: number, bar: number, unit: Unit): { side: number[]; total: number } {
  let per = (target - bar) / 2;
  const side: number[] = [];
  for (const p of PLATES[unit]) while (per >= p - 1e-9) { side.push(p); per -= p; }
  return { side, total: bar + 2 * side.reduce((a, p) => a + p, 0) };
}

export function PlateCalc({ initial, unit: given }: { initial?: number; unit?: Unit }) {
  const s = useStore();
  const [unit, setUnit] = useState<Unit>(given ?? s.settings.unit);
  const [text, setText] = useState(initial ? String(initial) : "");
  const [bar, setBar] = useState(BARS[unit][0]);
  const typed = parseNum(text);
  const tooHeavy = typed !== undefined && typed > MAX_LOAD[unit];
  const target = tooHeavy ? undefined : typed;
  const result = target !== undefined ? platesFor(target, bar, unit) : null;
  const max = PLATES[unit][0];

  const switchUnit = (u: Unit) => { setUnit(u); setBar(BARS[u][0]); };

  return (
    <div className="plates stack">
      <div className="plates-inputs">
        <label className="field">
          <span>Weight to load ({unit})</span>
          <input className="input" inputMode="decimal" value={text} onChange={(e) => setText(e.target.value)} placeholder={unit === "kg" ? "100" : "225"} aria-label={`Weight to load in ${unit}`} />
        </label>
        <div className="field">
          <span id="bar-label">Bar</span>
          <div className="segmented" role="radiogroup" aria-labelledby="bar-label" style={{ gridTemplateColumns: `repeat(${BARS[unit].length}, minmax(0, 1fr))` }}>
            {BARS[unit].map((b) => <button key={b} type="button" role="radio" aria-checked={bar === b} onClick={() => setBar(b)}>{b ? `${b} ${unit}` : "None"}</button>)}
          </div>
        </div>
      </div>
      <div className="segmented two-up unit-switch" role="radiogroup" aria-label="Plate unit">
        {(["kg", "lb"] as Unit[]).map((u) => <button key={u} type="button" role="radio" aria-checked={unit === u} onClick={() => switchUnit(u)}>{u === "kg" ? "Kilograms" : "Pounds"}</button>)}
      </div>
      {tooHeavy && <div className="plates-result" aria-live="polite"><p>Enter a weight up to {MAX_LOAD[unit]} {unit}.</p></div>}
      {result && target !== undefined && (
        <div className="plates-result" aria-live="polite">
          {target < bar ? (
            <p>That is less than the bar ({bar} {unit}).</p>
          ) : (
            <>
              <p className="plates-line">
                {result.side.length ? <>On each side: <strong>{result.side.map((p) => showW(p, "kg")).join(" + ")}</strong> {unit}</> : <>Just the bar.</>}
              </p>
              {Math.abs(result.total - target) > 1e-9 && (
                <p className="muted small">The closest you can load with standard plates is {showW(result.total, "kg")} {unit}, {showW(target - result.total, "kg")} {unit} under.</p>
              )}
              {result.side.length > 0 && (
                <div className="sleeve" aria-hidden="true">
                  <span className="sleeve-collar" />
                  {result.side.map((p, i) => (
                    <span key={i} className="plate" style={{ height: `${36 + (p / max) * 64}px`, width: `${p >= max * 0.5 ? 16 : 11}px`, opacity: 0.55 + (p / max) * 0.45 }} title={`${p} ${unit}`} />
                  ))}
                  <span className="sleeve-end" />
                </div>
              )}
            </>
          )}
        </div>
      )}
      <p className="hint">Standard plates: {PLATES[unit].join(", ")} {unit}.</p>
    </div>
  );
}

Change history

Point OpenApps store links to openapps.free
f9f4e37d69 · 1 day ago
Add favicon.ico, local share image and openapi.json for links and guides
2796c0063c · 1 day ago
Bind agent address in policy constants
cf80a3ca66 · 1 day ago
Add OpenApp contribution strip and OpenApps source links
5e4cc2b9f3 · 1 day ago
Bounded source sync
ca4a3d3631 · 2 days ago
Bounded source sync
7351063f4d · 2 days ago
Bounded source sync
0ce3238c46 · 2 days ago