Staircase3D
HjemAnnonserLogg inn
EN
For nettsideutviklere

Mottak av trappdata

Registrer bare bedriftens HTTPS-nettadresse. Når kunden velger å dele trappen, følger opplysningene med som query-parametere i samme GET-forespørsel som åpner nettsiden. Det kreves ingen API-nøkkel eller separat mottaksadresse. Nettsiden kan ignorere opplysningene eller bruke dem i et tilbudsskjema. Åpner kunden nettsiden uten deling, legger appen ikke ved trappdata.

  1. Legg nettadressen til tilbudssiden din i bedriftskortet.
  2. Kunden velger bedriften i appen og deler trappen når nettsiden åpnes.
  3. Nettsiden leser feltene fra nettadressen. Fyll ut tilbudsskjemaet automatisk, eller bruk mengdene i din egen priskalkulator.

Felter i nettadressen

FeltInnhold
staircalc_versionLenkeformatets versjon: 1.
staircalc_width_mm / staircalc_depth_mmSamlet utstrekning sett ovenfra, ikke løpsbredde.
staircalc_height_mmSamlet høyde fra nederste til øverste nivå.
staircalc_levelsAntall trappenivåer.
staircalc_detailsJSON-liste med trappevalg for hvert nivå, se nedenfor.
staircalc_quantitiesValgfritt JSON-objekt med mengdegrunnlaget for prisestimatet.
staircalc_data_urlValgfri hentelenke til hele grunnlaget. Utløper etter én time.

Alle mål er i millimeter med punktum som desimalskilletegn. JSON-feltene er URL-kodet; URLSearchParams dekoder dem før JSON.parse. Appen bevarer nettsidens eksisterende parametere og fragment. Den ferdige lenken begrenses til 8 000 byte og deles aldri ved å kutte bort hjørner eller nivåer. En nettside med en lavere lengdegrense kan kreve tilpasning.

Trappevalg per nivå

[
  {
    "level": 1,
    "steps": 15,
    "rises": 16,
    "riser_mm": 175,
    "floor_height_mm": 2800,
    "tread_mm": 270,
    "tread_thickness_mm": 40,
    "flight_widths_mm": [
      1000,
      1000
    ],
    "nose": "rounded_top",
    "nose_mm": 25,
    "nose_overrides": [],
    "riser_board": "full",
    "stringer": "supported",
    "corners": [
      {
        "corner": 1,
        "type": "winder",
        "turn": "left",
        "angle_deg": 90
      }
    ],
    "inline_landing_flights": [],
    "merged_landing_flights": []
  }
]

steps er antall fysiske trinn uten reposer. rises er antall høydeøkninger og riser_mm er faktisk opptrinnshøyde. Disse to feltene utelates hvis de ikke kan fastslås fra et eldre arkiv. tread_mm er innstilt inntrinn langs ganglinjen. flight_widths_mm oppgir bredden for hvert løp. Alle nivåer, trinn, løp og hjørner nummereres fra 1, nedenfra.

nose: none, square, rounded_top, rounded_top_bottom, samba_square eller samba_rounded. nose_mm er felles neseutstikk. nose_overrides oppgir egne valg for enkelttrinn som objekter med step, type og projection_mm. riser_board: none, full, child_safety eller shaped_child_safety. stringer: closed_simple, closed_smooth, glued, supported, intermediate_support eller boxed_winder.

Hvert hjørne har type landing (repos) eller winder (vindel), turn left/right og vinkel i grader. inline_landing_flights viser rette løp som er hvilereposer. merged_landing_flights viser løp mellom hjørner som er slått sammen til et felles repos.

Mengder og prisestimat

{
  "stringerMeters": 8.4,
  "steps": 15,
  "riserBoards": 15,
  "winderCorners": 1,
  "landingNodes": 0
}

På kontosiden velger du valuta og enhetspriser inkludert mva. Estimatet er summen av vangemeter, trinn, stusstrinn, vindelhjørner og reposnoder multiplisert med dine priser. Vangemeter bruker samlet emnelengde for de fysiske vangedelene uten kappsvinn. Reposer telles per hjørne, også ved sammenslåtte reposer, pluss én gang per rett hvilerepos. Bunn- og toppavslutninger regnes ikke som reposnoder. Manglende pris for en mengde som faktisk inngår gir «pris på forespørsel»; 0 betyr uttrykkelig inkludert. Laveste komplette estimat vises først innenfor samme valuta. Frakt, montering og rekkverk er ikke med i estimatet.

Bruk feltene på nettsiden

Dette eksemplet fyller et skjema med første trappenivå. Legg skriptet etter skjemaet, eller last det med defer. Tilpass feltnavnene til nettsiden din. For flere nivåer bruker du ett sett med felt per element i details.

<form id="staircase-quote">
  <label>Steps <input name="steps" type="number"></label>
  <label>Riser height (mm) <input name="riser_mm" type="number" step="any"></label>
  <label>Nosing <input name="nose"></label>
  <label>Riser board <input name="riser_board"></label>
  <label>Corners <textarea name="corners"></textarea></label>
</form>
<p id="staircase-data-status" role="status"></p>
const query = new URLSearchParams(window.location.search);
function readJSON(name, fallback) {
  try { return JSON.parse(query.get(name) || "null") ?? fallback; }
  catch { return fallback; }
}
const supported = query.get("staircalc_version") === "1";
const rawDetails = supported ? readJSON("staircalc_details", []) : [];
const details = Array.isArray(rawDetails) ? rawDetails : [];
const quantities = supported ? readJSON("staircalc_quantities", null) : null;
const firstLevel = details[0];
const form = document.querySelector("#staircase-quote");
function fill(name, value) {
  const field = form?.elements.namedItem(name);
  if (field && "value" in field && !field.value && value != null) {
    field.value = String(value); // Do not insert URL data as HTML.
  }
}
if (firstLevel && typeof firstLevel === "object") {
  if (Number.isInteger(firstLevel.steps) && firstLevel.steps >= 0) {
    fill("steps", firstLevel.steps);
  }
  if (Number.isFinite(firstLevel.riser_mm) && firstLevel.riser_mm > 0) {
    fill("riser_mm", firstLevel.riser_mm);
  }
  if (typeof firstLevel.nose === "string") fill("nose", firstLevel.nose);
  if (typeof firstLevel.riser_board === "string") fill("riser_board", firstLevel.riser_board);
  if (Array.isArray(firstLevel.corners)) {
    fill("corners", JSON.stringify(firstLevel.corners));
  }
}

Vanlige besøk uten trappdata lar skjemaet stå tomt. Kunden kan kontrollere og endre de utfylte feltene før innsending. Valider alle verdier på serveren før du beregner et bindende tilbud. Har du allerede et skjema i et publiseringssystem, kan nettutvikleren din koble de samme feltene til det.

Valgfritt: hent hele grunnlaget

Den midlertidige lenken er en tilgangsnøkkel. Alle som har den kan hente dataene i én time. Hele grunnlaget lagres kryptert; utløpte rader ryddes hvert minutt. Henting stoppes også hvis nettsideadressen endres eller bedriftskortet ikke lenger er aktivt i valgt område. Nettleserkode kan hente med CORS uten innlogging. Lenken trenger ingen leverandørnøkkel og skal ikke logges eller deles videre. Hovedmål og trappevalg kan fortsatt følge nettsidelenken dersom full deling er utilgjengelig.

Fortsett i samme skript med eksemplet under hvis du trenger mer enn feltene i lenken. Du kan bruke project.levels til egen beregning eller la kunden sende grunnlaget sammen med tilbudsskjemaet til din server. Hent dataene mens lenken er gyldig; den er ikke en varig arkivlenke.

async function receiveCompleteStaircase() {
  const source = supported ? query.get("staircalc_data_url") : null;
  if (!source) return null;
  try {
    const url = new URL(source);
    if (url.origin !== "https://aedbbkplnmmimkacxrup.supabase.co" ||
        url.pathname !== "/functions/v1/get-project-share" ||
        url.username || url.password ||
        !/^[0-9a-f]{64}$/.test(url.searchParams.get("token") || "")) {
      throw new Error("Invalid Staircase3D data URL");
    }
    const response = await fetch(url, {
      credentials: "omit", cache: "no-store", redirect: "error",
      referrerPolicy: "no-referrer", signal: AbortSignal.timeout(10000)
    });
    if (!response.ok) throw new Error("Staircase data unavailable");
    const project = await response.json();
    if (project.schemaVersion !== 1 || !Array.isArray(project.levels)) {
      throw new Error("Unsupported staircase data");
    }
    return project;
  } catch {
    const status = document.querySelector("#staircase-data-status");
    if (status) status.textContent =
      "Complete data is unavailable. You can still use the prefilled form.";
    return null;
  }
}
// For example, inside your async form-initialization function:
// const project = await receiveCompleteStaircase();
// project?.levels[n].input: complete editable staircase input
// project?.levels[n].summary: summary for this level
// project?.pricingBasis: quantities for your own price calculation
receiveCompleteStaircase().then((project) => {
  if (!project) return;
  const status = document.querySelector("#staircase-data-status");
  if (status) status.textContent =
    "Received " + project.levels.length + " staircase level(s).";
  // Connect project to your own quote form or calculator here.
});

Hele JSON-objektet har schemaVersion, deliveryID, sentAt, units, overallDimensions, levels og valgfritt pricingBasis. Hvert nivå har id, levelNumber, elevationMillimeters, dimensions, input og summary. input inneholder hele det redigerbare trappegrunnlaget. Prosjektnavn, bilder, notater, delearkiv og 3D-meshdata følger ikke med. Opplysningene er et kundelevert planleggingsgrunnlag, ikke en bekreftet ordre.

© 2026 Widding Systems AS
Trappedata på nettsidenPersonvernVilkårWidding Systems