```ts
/**
 * Implements nameStepDistances as per the given task contract.
 * @param steps - A list of one or more steps, each a list of exactly two whole numbers.
 * @returns A mapping with keys "names", "lifts", "colours", "tally", and "widest".
 */
export function nameStepDistances(steps: any[]): any {
  if (!Array.isArray(steps) || steps.length === 0) {
    throw new Error("Invalid input: steps must be a non-empty array.");
  }

  const stepNames = [
    "unison",
    "minor second",
    "major second",
    "minor third",
    "major third",
    "perfect fourth",
    "tritone",
    "perfect fifth",
    "minor sixth",
    "major sixth",
    "minor seventh",
    "major seventh"
  ];

  const sweetLeftovers = new Set([0, 3, 4, 5, 7, 8, 9]);
  const sharpLeftovers = new Set([1, 2, 6, 10, 11]);

  const result: {
    names: string[];
    lifts: number[];
    colours: string[];
    tally: { [key: string]: number };
    widest: number;
  } = {
    names: [],
    lifts: [],
    colours: [],
    tally: {},
    widest: -1
  };

  let maxReach = 0;

  steps.forEach((step, index) => {
    if (!Array.isArray(step) || step.length !== 2) {
      throw new Error("Invalid input: each step must be a list of exactly two numbers.");
    }

    const [mark1, mark2] = step;
    if (!Number.isInteger(mark1) || !Number.isInteger(mark2)) {
      throw new Error("Invalid input: pitch marks must be whole numbers.");
    }

    const reach = Math.abs(mark1 - mark2);
    const lift = Math.floor(reach / 12);
    const leftover = reach % 12;
    const name = stepNames[leftover];
    const colour = sweetLeftovers.has(leftover) ? "sweet" : sharpLeftovers.has(leftover) ? "sharp" : "";

    result.names.push(name);
    result.lifts.push(lift);
    result.colours.push(colour);

    if (reach > maxReach) {
      maxReach = reach;
      result.widest = index;
    }

    if (!result.tally[name]) {
      result.tally[name] = 0;
    }
    result.tally[name]++;
  });

  return result;
}
```