# Stair (ISO 14122-3)

Straight steel stair after ISO 14122-3: UPN channel stringers with level-cut feet on anchored cleats, bar-grating treads with yellow chequer nosings and bolted end plates, even risers near 180 mm with the going set by the pitch, an intermediate landing on posts for every 3 m of rise, and handrails with knee rails on both sides. Foot (first riser) at the origin on y = 0, climbing along +X; the upper floor edge is at x = run = (round(rise/0.18) − 1) × going per flight, plus landingLength per intermediate landing. Optional free-standing top landing.

**Standards:** ISO 14122-3, EN ISO 14122-1, NORSOK C-001, OSHA 1910.25

## Installation

```bash
npx -y https://decktwin.com/cli/deck-twin-0.2.2.tgz add stair
```

Copies `structure/stair.tsx`, any helper files it imports and the shared `lib/` into `src/components/deck-twin/`. Needs `three`, `@react-three/fiber` and `@react-three/drei`.

## Usage

```tsx
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
import { Stage } from './components/deck-twin/lib/stage'
import { Stair } from './components/deck-twin/structure/stair'

export default function Scene() {
  return (
    <Canvas shadows camera={{ position: [8, 5, 9], fov: 35 }}>
      <Stage>
        <Stair rise={4.5} topLanding={true} />
      </Stage>
      <OrbitControls makeDefault />
    </Canvas>
  )
}
```

## Props

| Prop | Type | Default | Range |
| --- | --- | --- | --- |
| `rise` | `number (m)` | `3` | 0.6 – 12 |
| `width` | `number (m)` | `1` | 0.6 – 1.6 |
| `angle` | `number (°)` | `35` | 30 – 45 |
| `stringer` | `"UPN 160" \| "UPN 180" \| "UPN 200" \| "UPN 240"` | `"UPN 200"` |  |
| `landingLength` | `number (m)` | `1` | 0.8 – 2 |
| `topLanding` | `boolean` | `false` |  |
| `finish` | `"galvanised" \| "painted"` | `"galvanised"` |  |
| `...group` | position, rotation, scale… |  |  |

## P&ID

Plan view: a rectangle filled with parallel tread lines and an arrow along it labelled UP (or DN), often with a break line half-way.

Layout plan convention (structural / plot plan drawings); ISO 14122-3

Rungs at close spacing with a circle around them is a caged ladder seen in plan. Not on P&IDs.

## Typical suppliers

Fabricated to project specification. The geometry is generic and sized from public standards. It is not vendor CAD.

## Source

`structure/stair.tsx`. This is the component file only: it imports shared code, so install it with the command above rather than pasting it alone. https://decktwin.com/r/stair.json has every file.

```tsx
// Straight stair after ISO 14122-3: UPN stringers, grating treads with chequer nosings, intermediate landings every ≤ 3 m of rise, handrails both sides.
import type { Kit, MatKey } from '../lib/kit'
import { KitMesh, useKit, type GroupProps } from '../lib/mesh'
import { stairRun } from './_access'

export interface StairProps {
  /** total height from foot to upper floor, m */
  rise?: number
  /** clear width between stringers, m */
  width?: number
  /** pitch, degrees (ISO 14122-3: 30–38 preferred) */
  angle?: number
  stringer?: string
  /** landing length along X for intermediate / top landings, m */
  landingLength?: number
  /** free-standing top landing on posts beyond the last riser */
  topLanding?: boolean
  finish?: 'galvanised' | 'painted'
}

export const defaults = {
  rise: 3,
  width: 1.0,
  angle: 35,
  stringer: 'UPN 200',
  landingLength: 1.0,
  topLanding: false,
  finish: 'galvanised' as 'galvanised' | 'painted',
}

type P = typeof defaults

export function Stair(props: StairProps & GroupProps) {
  const { rise, width, angle, stringer, landingLength, topLanding, finish, ...group } = { ...defaults, ...props }
  const data = useKit((k) => build(k, { rise, width, angle, stringer, landingLength, topLanding, finish }), [rise, width, angle, stringer, landingLength, topLanding, finish])
  return (
    <group {...group}>
      <KitMesh data={data} />
    </group>
  )
}

export default Stair

function build(k: Kit, p: P) {
  const mat: MatKey = p.finish === 'painted' ? 'safetyYellow' : 'galvanised'
  stairRun(k, p.rise, { width: Math.max(0.6, p.width), angle: p.angle, stringer: p.stringer, mat, landingLength: p.landingLength, topLanding: p.topLanding })
}
```
