// Registration-form steps. Each step is a React component that receives
// the form data, an updater, and per-field errors. The orchestrator in
// registration-app.jsx decides which step to show.

// Countries list (separate from nationalities — what country are you based
// in right now, regardless of passport).
const REG_COUNTRIES = [
  "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Canada",
  "Chile", "China", "Colombia", "Croatia", "Czech Republic", "Denmark",
  "Egypt", "Estonia", "Finland", "France", "Germany", "Greece", "Hong Kong",
  "Hungary", "Iceland", "India", "Indonesia", "Ireland", "Israel", "Italy",
  "Japan", "Jordan", "Kazakhstan", "Latvia", "Lithuania", "Luxembourg",
  "Malaysia", "Mexico", "Monaco", "Morocco", "Netherlands", "New Zealand",
  "Norway", "Philippines", "Poland", "Portugal", "Qatar", "Romania",
  "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia",
  "South Africa", "South Korea", "Spain", "Sweden", "Switzerland",
  "Thailand", "Turkey", "UAE", "UK", "Ukraine", "USA", "Vietnam", "Other",
];

// ─── Step 0 — Welcome ──────────────────────────────────────────────────
function StepWelcome({ onStart }) {
  return (
    <div style={{
      display:"flex", flexDirection:"column", alignItems:"center",
      textAlign:"center", gap: 36, maxWidth: 720,
    }}>
      <div style={{
        fontFamily:"var(--mono)", fontSize: 11, letterSpacing:".22em",
        textTransform:"uppercase", color:"rgba(243,239,233,0.55)",
      }}>
        Maison Reverie · Applicants · 2026
      </div>

      <h1 style={{
        fontFamily:"var(--font-display)", fontWeight: 500,
        letterSpacing:"-0.04em", lineHeight: 0.9,
        fontSize: "clamp(72px, 9vw, 140px)",
        margin: 0,
      }}>
        Apply
      </h1>

      <div style={{
        fontFamily:"var(--font-display)", fontStyle:"italic",
        fontSize:"clamp(20px, 2vw, 28px)", lineHeight: 1.45,
        color:"rgba(243,239,233,0.78)",
        maxWidth: 560,
      }}>
        Tell us a little about yourself. The next eight steps build the
        editorial profile our bookers see — the more honest and specific
        you can be, the better the work we send your way.
      </div>

      <div style={{ display:"flex", gap: 12, alignItems:"center", marginTop: 8 }}>
        <button className="reg-btn primary" onClick={onStart}>Begin <span className="arrow">→</span></button>
      </div>

      <div style={{
        fontFamily:"var(--mono)", fontSize: 10.5, letterSpacing:".18em",
        textTransform:"uppercase", color:"rgba(243,239,233,0.4)", marginTop: 4,
      }}>
        — takes about 6 minutes
      </div>
    </div>
  );
}

// ─── Step 1 — Basics ───────────────────────────────────────────────────
function StepBasics({ data, update, errors }) {
  return (
    <StepShell title="Basics" eyebrow="01 / Personal">
      <RegText label="Real first name" value={data.firstName}
        onChange={(v) => update("firstName", v)}
        placeholder="Elena" required autoFocus
        help="Staff-only. Never shown on your public profile."
        error={errors.firstName}/>
      <RegText label="Real last name" value={data.lastName}
        onChange={(v) => update("lastName", v)}
        placeholder="Voss" required
        help="Staff-only. Never shown on your public profile."
        error={errors.lastName}/>
      <RegText label="Alias" value={data.alias}
        onChange={(v) => update("alias", v)}
        placeholder="Elena V." required
        help="The name members will see on your profile. Pick one you'll respond to comfortably."
        error={errors.alias}/>
      <RegDateParts label="Date of birth" value={data.dob}
        onChange={(v) => update("dob", v)} required
        help="Your age is calculated and displayed on the profile — the full date is never shown."
        error={errors.dob}/>
      <RegText label="Email" value={data.email}
        onChange={(v) => update("email", v)}
        placeholder="you@example.com" required
        help="Staff-only. Used by the desk to reach you about bookings."
        error={errors.email}/>
      <RegText label="WhatsApp number" value={data.phone}
        onChange={(v) => update("phone", v)}
        placeholder="+44 7700 900000" required
        help="Staff-only. With country code. We send urgent booking details here."
        error={errors.phone}/>
    </StepShell>
  );
}

// ─── Step 2 — Measurements ─────────────────────────────────────────────
function StepMeasurements({ data, update, errors }) {
  const O = window.REG_OPTIONS;
  return (
    <StepShell title="Measurements" eyebrow="02 / The numbers">
      <RegHeight value={data.heightCm}
        onChange={(v) => update("heightCm", v)} required
        error={errors.heightCm}/>

      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap: 32, alignItems:"start" }}>
        <RegDropdown label="Cup size" value={data.cupSize}
          options={O.cupSizes}
          onChange={(v) => update("cupSize", v)} required
          error={errors.cupSize}/>
        <RegDropdown label="Band size" value={data.bandSize}
          options={O.bandSizes}
          onChange={(v) => update("bandSize", v)} required
          error={errors.bandSize}/>
      </div>

      <RegRadio label="Breasts" value={data.breasts}
        options={O.breasts}
        onChange={(v) => update("breasts", v)} required
        error={errors.breasts}/>

      <RegSizeWithRegion label="Dress size" value={data.dressSize}
        table={window.REG_SIZE_TABLES.dress}
        onChange={(v) => update("dressSize", v)} required
        error={errors.dressSize}/>

      <RegSizeWithRegion label="Shoe size" value={data.shoeSize}
        table={window.REG_SIZE_TABLES.shoe}
        onChange={(v) => update("shoeSize", v)} required
        error={errors.shoeSize}/>
    </StepShell>
  );
}

// ─── Step 3 — Appearance ───────────────────────────────────────────────
function StepAppearance({ data, update, errors }) {
  const O = window.REG_OPTIONS;
  return (
    <StepShell title="Appearance" eyebrow="03 / Hair, eyes, marks">
      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap: 32 }}>
        <RegDropdown label="Hair colour" value={data.hairColor}
          options={O.hairColors}
          onChange={(v) => update("hairColor", v)} required
          error={errors.hairColor}/>
        <RegDropdown label="Hair length" value={data.hairLength}
          options={O.hairLengths}
          onChange={(v) => update("hairLength", v)} required
          error={errors.hairLength}/>
      </div>

      <RegDropdown label="Eye colour" value={data.eyeColor}
        options={O.eyeColors}
        onChange={(v) => update("eyeColor", v)} required
        error={errors.eyeColor}/>

      <RegYesNoDetails label="Tattoos" value={data.tattoos}
        onChange={(v) => update("tattoos", v)}
        detailLabel="Where, and roughly how large?"/>

      <RegYesNoDetails label="Piercings" value={data.piercings}
        onChange={(v) => update("piercings", v)}
        detailLabel="Where? (e.g. single lobe, nose)"/>
    </StepShell>
  );
}

// ─── Step 4 — Origins ──────────────────────────────────────────────────
function StepOrigins({ data, update, errors }) {
  const O = window.REG_OPTIONS;
  return (
    <StepShell title="Origins" eyebrow="04 / Nationality, base, languages">
      <RegDropdown label="Nationality" value={data.nationality}
        options={O.nationalities}
        onChange={(v) => update("nationality", v)} required
        error={errors.nationality}/>

      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap: 32 }}>
        <RegText label="Based in — city" value={data.basedInCity}
          onChange={(v) => update("basedInCity", v)}
          placeholder="Your city" required
          error={errors.basedInCity}/>
        <RegDropdown label="Based in — country" value={data.basedInCountry}
          options={REG_COUNTRIES}
          onChange={(v) => update("basedInCountry", v)} required
          error={errors.basedInCountry}/>
      </div>

      <RegMultiChip label="Languages you speak"
        value={data.languages} options={O.languages}
        onChange={(v) => update("languages", v)} required
        help="Tap any you can hold a working conversation in."
        error={errors.languages}/>
    </StepShell>
  );
}

// ─── Step 5 — Working with you ─────────────────────────────────────────
function StepWork({ data, update, errors }) {
  const O = window.REG_OPTIONS;
  return (
    <StepShell title="Working with you" eyebrow="05 / Availability">
      <RegDropdown label="Availability" value={data.availability}
        options={O.availability}
        onChange={(v) => update("availability", v)} required
        help="How much heads-up bookers should give you."
        error={errors.availability}/>

      <RegDropdown label="Travel" value={data.travel}
        options={O.travel}
        onChange={(v) => update("travel", v)} required
        help="Regions you're willing to fly to."
        error={errors.travel}/>
    </StepShell>
  );
}

// ─── Step 6 — In your words ────────────────────────────────────────────
function StepWords({ data, update, errors }) {
  return (
    <StepShell title="In your words" eyebrow="06 / Bio + personality">
      <RegTextArea
        label="Short biography"
        value={data.bio}
        onChange={(v) => update("bio", v)}
        placeholder="A few sentences. Where you trained, the kind of work you like, what you bring to a set."
        rows={5} required
        help="The professional voice — reads under the hero of your profile."
        error={errors.bio}/>

      <RegTextArea
        label="Personality"
        value={data.personality}
        onChange={(v) => update("personality", v)}
        placeholder="Two or three lines about you off-set. What you read, where you go, the things you can't help mentioning."
        rows={4} required
        help="The casual voice — a counterpoint to the bio. Italic on the profile."
        error={errors.personality}/>
    </StepShell>
  );
}

// ─── Step 7 — Portfolio ────────────────────────────────────────────────
function StepPortfolio({ data, update, errors }) {
  const MIN = window.REG_MIN_MEDIA;
  const MAX = window.REG_MAX_MEDIA;
  return (
    <StepShell title="Portfolio" eyebrow="07 / Photos & video">
      <div style={{
        fontFamily:"var(--font-display)", fontStyle:"italic",
        fontSize:"clamp(17px, 1.5vw, 22px)", lineHeight: 1.5,
        color:"rgba(243,239,233,0.72)", maxWidth: 640,
      }}>
        Submit at least <strong style={{color:"#f3efe9", fontStyle:"normal"}}>{MIN}</strong> images so the
        gallery has rhythm. Aim for a mix — a close beauty, a full-length,
        an editorial, and a movement clip if you have one.
      </div>

      <RegFileUpload
        label="Upload" value={data.media}
        onChange={(v) => update("media", v)}
        min={MIN} max={MAX} required
        error={errors.media}/>
    </StepShell>
  );
}

// ─── Step 8 — Review & submit ──────────────────────────────────────────
function StepReview({ data, onEditStep, onSubmit, submitting }) {
  const fmt = window.REG_FORMATTERS;
  const lines = [
    ["Real name",     `${data.firstName || ""} ${data.lastName || ""}`.trim() || "—"],
    ["Alias",         data.alias || "—"],
    ["Age",           fmt.age(data.dob)],
    ["Email",         data.email || "—"],
    ["WhatsApp",      data.phone || "—"],
    ["Height",        fmt.height(data.heightCm)],
    ["Cup size",      data.cupSize || "—"],
    ["Band",          data.bandSize || "—"],
    ["Breasts",       data.breasts || "—"],
    ["Dress",         data.dressSize ? `EU ${data.dressSize}` : "—"],
    ["Shoe",          data.shoeSize ? `EU ${data.shoeSize}` : "—"],
    ["Hair",          [data.hairColor, data.hairLength].filter(Boolean).join(", ") || "—"],
    ["Eyes",          data.eyeColor || "—"],
    ["Marks",         fmt.marks(data.tattoos, data.piercings)],
    ["Nationality",   data.nationality || "—"],
    ["Based in",      fmt.basedIn(data.basedInCity, data.basedInCountry)],
    ["Languages",     (data.languages || []).join(", ") || "—"],
    ["Availability",  data.availability || "—"],
    ["Travel",        data.travel || "—"],
    ["Media",         `${(data.media || []).length} files`],
  ];

  return (
    <StepShell title="Review" eyebrow="08 / Almost done">
      <div style={{
        fontFamily:"var(--font-display)", fontStyle:"italic",
        fontSize:"clamp(18px, 1.7vw, 24px)", lineHeight: 1.5,
        color:"rgba(243,239,233,0.75)", maxWidth: 600,
      }}>
        Glance over the values below. Anything off? Jump back to fix it.
      </div>

      <div style={{
        marginTop: 28,
        display:"grid", gridTemplateColumns:"1fr 1fr",
        columnGap: 64, rowGap: 0,
        fontFamily:"var(--mono)", fontSize: 12,
        letterSpacing:".04em", textTransform:"uppercase",
      }}>
        {lines.map(([k, v]) => (
          <div key={k} style={{
            display:"grid", gridTemplateColumns:"140px 1fr",
            padding:"14px 0",
            borderBottom:"1px solid rgba(243,239,233,0.10)",
            alignItems:"baseline",
          }}>
            <span style={{ color:"rgba(243,239,233,0.45)" }}>{k}</span>
            <span style={{ color:"#f3efe9", fontWeight:500 }}>{v}</span>
          </div>
        ))}
      </div>

      <div style={{ display:"flex", gap:12, marginTop: 32, alignItems:"center" }}>
        <button className="reg-btn ghost" onClick={() => onEditStep(1)}>
          ← Edit from start
        </button>
        <button className="reg-btn primary" onClick={onSubmit} disabled={submitting}>
          {submitting ? "Submitting…" : <>Submit application <span className="arrow">→</span></>}
        </button>
      </div>
    </StepShell>
  );
}

// ─── Step 9 — Thank you + live profile preview ─────────────────────────
function StepDone({ data, onDownloadData }) {
  const model = window.REG_BUILD_MODEL(data);
  const config = {
    theme: "dark", type: "grotesque-led",
    hero: "name-overlay", gallery: "justified",
    statsDensity: "full", mediaCount: 100,
  };
  return (
    <div style={{ width:"100%", display:"flex", flexDirection:"column", gap: 64, alignItems:"stretch" }}>
      {/* Thank-you header — centred */}
      <div style={{
        display:"flex", flexDirection:"column", alignItems:"center",
        textAlign:"center", gap: 28, maxWidth: 720, marginInline: "auto",
      }}>
        <div style={{
          fontFamily:"var(--mono)", fontSize: 11, letterSpacing:".22em",
          textTransform:"uppercase", color:"rgba(243,239,233,0.55)",
        }}>
          Application received · file logged
        </div>

        <h1 style={{
          fontFamily:"var(--font-display)", fontWeight: 500,
          letterSpacing:"-0.04em", lineHeight: 0.9,
          fontSize: "clamp(56px, 7vw, 108px)",
          margin: 0,
        }}>
          Thank you,<br/><em style={{fontStyle:"italic"}}>{data.alias || data.firstName || "applicant"}</em>.
        </h1>

        <div style={{
          fontFamily:"var(--font-display)", fontStyle:"italic",
          fontSize:"clamp(18px, 1.6vw, 24px)", lineHeight: 1.45,
          color:"rgba(243,239,233,0.78)",
          maxWidth: 580,
        }}>
          We read every file. We aim to reply within <em style={{ color: "#f3efe9", fontStyle: "normal" }}>48 hours</em>. Your draft profile is below — it's exactly
          what bookers will see.
        </div>
      </div>

      {/* Preview header strip */}
      <div style={{
        display:"flex", alignItems:"baseline", justifyContent:"space-between",
        borderTop: "1px solid rgba(243,239,233,0.16)",
        borderBottom: "1px solid rgba(243,239,233,0.16)",
        padding: "18px 0",
        fontFamily:"var(--mono)", fontSize: 10.5, letterSpacing:".22em",
        textTransform:"uppercase", color:"rgba(243,239,233,0.62)",
      }}>
        <span>Your draft profile</span>
        <span style={{ color:"rgba(243,239,233,0.4)" }}>
          Live preview · 1:1 with the booker view
        </span>
        <button
          type="button"
          onClick={onDownloadData}
          style={{
            appearance:"none", border:0, background:"transparent",
            color:"rgba(243,239,233,0.6)", cursor:"pointer",
            font:"inherit", letterSpacing:"inherit", textTransform:"inherit",
          }}
          onMouseEnter={(e) => e.currentTarget.style.color = "#f3efe9"}
          onMouseLeave={(e) => e.currentTarget.style.color = "rgba(243,239,233,0.6)"}
        >
          Download data ↓
        </button>
      </div>

      {/* The actual profile, rendered identical to the production template */}
      <div style={{
        margin: "0 calc(50% - 50vw)", /* break out of the centred form container */
        width: "100vw",
        boxShadow:"0 0 0 1px rgba(243,239,233,0.08), 0 30px 80px rgba(0,0,0,0.6)",
      }}>
        <ProfileClinical model={model} tweaks={config}/>
      </div>
    </div>
  );
}

// ─── StepShell — title + content frame ─────────────────────────────────
function StepShell({ title, eyebrow, children }) {
  return (
    <div style={{ width:"100%", maxWidth: 880, display:"flex", flexDirection:"column", gap: 32 }}>
      <header style={{ display:"flex", flexDirection:"column", gap: 14 }}>
        <span style={{
          fontFamily:"var(--mono)", fontSize: 10.5, letterSpacing:".22em",
          textTransform:"uppercase", color:"rgba(243,239,233,0.5)",
        }}>
          {eyebrow}
        </span>
        <h2 style={{
          fontFamily:"var(--font-display)", fontWeight: 500,
          letterSpacing:"-0.035em", lineHeight: 0.9,
          fontSize:"clamp(52px, 6.5vw, 96px)",
          margin: 0,
        }}>
          {title}
        </h2>
      </header>

      <div style={{ display:"flex", flexDirection:"column", gap: 32 }}>{children}</div>
    </div>
  );
}

// ─── Formatters — used in Review + when building the live model ────────
window.REG_FORMATTERS = {
  age(dob) {
    if (!dob) return "—";
    const d = new Date(dob);
    if (isNaN(d)) return "—";
    const now = new Date();
    let age = now.getFullYear() - d.getFullYear();
    const m = now.getMonth() - d.getMonth();
    if (m < 0 || (m === 0 && now.getDate() < d.getDate())) age--;
    return String(age);
  },
  height(cm) {
    if (!cm) return "—";
    const totalIn = cm / 2.54;
    const ft = Math.floor(totalIn / 12);
    const inch = Math.round(totalIn - ft * 12);
    return `${cm} cm · ${ft} ft ${inch} in`;
  },
  marks(t, p) {
    const parts = [];
    if (!t?.has) parts.push("No tattoos");
    else parts.push(t.detail ? `Tattoos: ${t.detail}` : "Tattoos");
    if (!p?.has) parts.push("no piercings");
    else parts.push(p.detail ? `piercings: ${p.detail}` : "piercings");
    return parts.join(", ");
  },
  basedIn(city, country) {
    if (!city && !country) return "—";
    return [city, country].filter(Boolean).join(", ");
  },
};

// ─── REG_BUILD_MODEL — shape registration data into the profile model ──
// Mirrors profile-data.js exactly so the live preview reads identical to
// what the production template renders.
const LANG_CODE = {
  English:"EN", French:"FR", Italian:"IT", Spanish:"ES", German:"DE",
  Portuguese:"PT", Russian:"RU", Polish:"PL", Ukrainian:"UA", Czech:"CZ",
  Hungarian:"HU", Greek:"GR", Turkish:"TR", Arabic:"AR", Hebrew:"HE",
  Persian:"FA", Hindi:"HI", Urdu:"UR", Bengali:"BN", Mandarin:"ZH",
  Cantonese:"YUE", Japanese:"JP", Korean:"KO", Thai:"TH",
  Vietnamese:"VN", Indonesian:"ID", Tagalog:"TL", Swahili:"SW", Dutch:"NL",
};

window.REG_BUILD_MODEL = function(data) {
  const F = window.REG_FORMATTERS;
  const tables = window.REG_SIZE_TABLES || {};

  const lookup = (table, eu) => {
    if (!eu || !table) return { eu: "", us: "", uk: "" };
    const i = table.EU.indexOf(String(eu));
    if (i < 0) return { eu, us: "", uk: "" };
    return { eu: table.EU[i], us: table.US[i], uk: table.UK[i] };
  };

  const dress = lookup(tables.dress, data.dressSize);
  const shoe  = lookup(tables.shoe,  data.shoeSize);

  const cmH = data.heightCm ? `${data.heightCm} cm` : "";
  const ftIn = data.heightCm
    ? (() => { const t = data.heightCm / 2.54; const ft = Math.floor(t / 12); const i = Math.round(t - ft * 12); return `${ft} ft ${i} in`; })()
    : "";

  const marks = (() => {
    const t = data.tattoos, p = data.piercings;
    if (!t?.has && !p?.has) return { v: "No tattoos", sub: "No piercings" };
    const parts = [];
    if (t?.has) parts.push(t.detail || "yes");  else parts.push("no tattoos");
    if (p?.has) parts.push(p.detail || "piercings"); else parts.push("no piercings");
    return { v: t?.has ? "Tattoos" : "Piercings", sub: parts.join(" · ") };
  })();

  const langs = data.languages || [];
  const langCodes = langs.slice(0, 4)
    .map((l) => LANG_CODE[l] || l.slice(0,2).toUpperCase())
    .join(" · ");

  const stats = [];
  const ageS = F.age(data.dob);
  if (ageS !== "—")    stats.push({ k:"Age",         v: ageS, sub: data.dob ? `b. ${new Date(data.dob).getFullYear()}` : "" });
  if (cmH)             stats.push({ k:"Height",      v: cmH, sub: ftIn });
  if (data.cupSize)    stats.push({ k:"Cup size",    v: data.cupSize, sub: data.bandSize });
  if (data.breasts)    stats.push({ k:"Breasts",     v: data.breasts });
  if (dress.eu)        stats.push({ k:"Dress",       v: `EU ${dress.eu}`, sub: `US ${dress.us} · UK ${dress.uk}` });
  if (shoe.eu)         stats.push({ k:"Shoe",        v: `EU ${shoe.eu}`,  sub: `US ${shoe.us} · UK ${shoe.uk}` });
  if (data.hairColor)  stats.push({ k:"Hair",        v: data.hairColor, sub: data.hairLength });
  if (data.eyeColor)   stats.push({ k:"Eyes",        v: data.eyeColor });
  if (data.nationality) stats.push({ k:"Nationality", v: data.nationality });
  if (data.basedInCity || data.basedInCountry) {
    stats.push({ k:"Based in", v: [data.basedInCity, data.basedInCountry].filter(Boolean).join(", ") });
  }
  if (langs.length)    stats.push({ k:"Languages",   v: langCodes, sub: langs.join(", ") });
  stats.push({ k:"Marks", v: marks.v, sub: marks.sub });
  if (data.availability) stats.push({ k:"Availability", v: data.availability });
  if (data.travel)       stats.push({ k:"Travel",      v: data.travel });

  // Always lead with an image so the hero isn't a video poster.
  const raw    = data.media || [];
  const images = raw.filter((m) => m.kind !== "video");
  const videos = raw.filter((m) => m.kind === "video");
  const ordered = images.length ? [...images, ...videos] : raw;

  const media = ordered.map((m, idx) => {
    const out = { i: idx + 1, src: m.src, w: m.w, h: m.h };
    if (m.kind === "video") out.kind = "video"; // poster left undefined — MediaItem handles it
    return out;
  });

  // Split the alias on the first whitespace so the hero can render its
  // two-line lockup. If the model picked a mononym ("Elena"), the second
  // line stays empty and the hero gracefully tightens.
  const aliasRaw = (data.alias || "").trim();
  const [aliasFirst, ...aliasRest] = aliasRaw.split(/\s+/);
  const aliasLast = aliasRest.join(" ");

  return {
    // Public-facing names — what every member, booker, and screenshot
    // ever sees. Real first/last live below in `private` (staff-only,
    // stripped before the profile is served to anyone other than the desk).
    firstName: aliasFirst || data.firstName || "First name",
    lastName:  aliasLast  || "",
    agency:    "Maison Reverie",
    city:      data.basedInCity || "—",
    stats,
    bio:          data.bio         || "",
    personality:  data.personality || "",
    availability: { notice: data.availability || "", travel: data.travel || "" },
    media,

    // Staff-only block — kept on the talent record in the DB but never
    // serialised onto a member-facing /talent/[slug] response. Used by
    // the desk when arranging an actual introduction.
    private: {
      realFirstName: data.firstName || "",
      realLastName:  data.lastName  || "",
      email:         data.email     || "",
      phone:         data.phone     || "",
    },
  };
};

Object.assign(window, {
  StepWelcome, StepBasics, StepMeasurements, StepAppearance, StepOrigins,
  StepWork, StepWords, StepPortfolio, StepReview, StepDone,
});
