Lighting & shadow model

mandatory

The light source model behind every shadow in Koder Design. Defines WHERE light comes from (key light position + ambient), HOW shadows are cast (umbra/penumbra, direction, softness), and how dark mode RE-LIGHTS rather than just dimming shadows. Elevation tokens (`elevation.kmd`) are the rendered output of this model; this spec is the source they derive from. Material parity: the implicit light model behind M3 elevation, made explicit and tokenized.

Facet Visual do Koder Design. Material parity: the light model implicit in https://m3.material.io/styles/elevation — Koder makes it explicit so shadows across 2D, 3D and XR stay coherent.

Elevation says which shadow a surface gets. Lighting says why that shadow looks the way it does — one virtual light rig, so a card on the web, a model-viewer mesh, and an XR panel all cast consistent shadows. Every --shadow-* token in elevation.kmd is a 2D projection of this model.

R1 — The light rig

Koder uses a two-source rig (Material/RealityKit convention):

Source Role Default
Key light Directional; casts the visible shadow From top, slightly front and left: azimuth −30°, elevation 60°
Ambient light Omnidirectional fill; lifts shadow floor Soft, ~40% intensity; no direction

Consequence (canonical): shadows fall down and slightly right of the element. Every preset shadow recipe MUST be consistent with this single direction — no per-element light angles (elevation.kmd § R5).

R2 — Shadow anatomy

A cast shadow is two layers, both tokenized:

Layer Purpose Driven by
Umbra (key shadow) Tight, darker contact shadow Depth level + key-light hardness
Penumbra (ambient shadow) Wide, soft diffusion Depth level + ambient intensity

This is exactly why elevation.kmd recipes are two box-shadow layers (a tight one + a wide one). Higher depth = larger offset + larger blur on both layers (object is farther from the surface it casts onto).

R3 — Softness tokens

Token Meaning Default
--kds-light-azimuth Horizontal light angle (deg) -30
--kds-light-elevation Vertical light angle (deg) 60
--kds-light-hardness Umbra sharpness 0(soft)–1(hard) 0.4
--kds-ambient-intensity Penumbra strength 01 0.4

Presets re-author the rig, not individual shadows:

Preset Rig change
verge (default) Canonical rig above
macos_sonoma Softer: hardness 0.2, longer penumbra
material_expressive Stronger key: hardness 0.6
windows_95 / brutalist / terminal_classic Light rig off — bevel/flat per elevation.kmd § R4
neumorphism Dual light (key + opposite fill) for inset/extrude

R4 — Dark mode is re-lighting, not dimming

The common mistake: in dark mode, keep the same shadow but lower its opacity. Wrong — on a dark surface a darker shadow is invisible.

Koder dark mode re-lights:

  1. Key shadow stays (darker, larger blur — elevation.kmd § R2

    dark recipe) but contributes little.

  2. Tonal overlay becomes the primary depth cue: the accent-tinted

    surface lightens with depth (the object catches more key light the closer it is). This is the --tonal-* system in elevation.kmd.

So: light mode = shadow-driven, dark mode = light-catch-driven (tonal lift). Same physical model, opposite dominant cue.

R5 — 3D & XR cast shadows

For real-geometry surfaces (components/model-viewer.kmd, XR panels):

  • The same rig (R1 azimuth/elevation) drives the renderer's

    directional light — a glTF mesh and a CSS card are lit from the same angle.

  • XR shadow distance (--kds-xr-shadow-distance, xr-preview.kmd § R3)

    is the spatial projection of --kds-light-elevation + depth level.

  • Contact shadow under a floating XR panel uses the umbra; the soft

    ground shadow uses the penumbra.

R6 — Forbidden patterns

  • ❌ Per-element light direction (breaks the single-rig illusion).
  • ❌ Shadows that fall up (light from below — uncanny except

    deliberate neumorphism inset).

  • ❌ Colored shadows other than the system's near-black/tonal pair

    (elevation.kmd § R5).

  • ❌ Dark-mode shadow that only dims opacity with no tonal lift (R4).
  • ❌ Hard-coded light angles in a renderer; always read the rig tokens.

R7 — Accessibility & forced colors

  • Light/shadow is never the sole signal of state or depth

    (elevation.kmd § R7).

  • Under forced-colors: active, the rig is inert — depth falls back

    to borders (elevation.kmd § R6).

  • prefers-reduced-transparency does not affect lighting (it affects

    materials.kmd), but reduced-motion freezes any animated light (e.g. drag-follow highlights).

T1-T3 — Tests

T1 — Single direction: across all canonical presets, every resolved --shadow-* recipe casts in the R1 direction (offset-y > 0, |offset-x| consistent sign). koder-spec-audit lighting --direction.

T2 — Dark re-light: in dark mode each depth level resolves a non-zero --tonal-* overlay (proves light-catch cue exists, not just dimming).

T3 — Rig coherence: a model-viewer directional light angle equals --kds-light-azimuth/elevation within 1°.

  • themes/elevation.kmd — the 2D rendered output of this model
  • themes/depth.kmd — the abstract z-axis this model illuminates
  • themes/light-dark.kmd — light/dark cue balance (R4)
  • components/model-viewer.kmd — real-geometry lighting (R5)
  • develop/xr-preview.kmd — spatial shadow projection (R5)