Pular para o conteúdo

Lighting & shadow model

themes specs/themes/lighting.kmd

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.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Lighting & shadow model

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):

SourceRoleDefault
Key lightDirectional; casts the visible shadowFrom top, slightly front and left: azimuth −30°, elevation 60°
Ambient lightOmnidirectional fill; lifts shadow floorSoft, ~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:

LayerPurposeDriven by
Umbra (key shadow)Tight, darker contact shadowDepth level + key-light hardness
Penumbra (ambient shadow)Wide, soft diffusionDepth 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

TokenMeaningDefault
--kds-light-azimuthHorizontal light angle (deg)-30
--kds-light-elevationVertical light angle (deg)60
--kds-light-hardnessUmbra sharpness 0(soft)–1(hard)0.4
--kds-ambient-intensityPenumbra strength 010.4

Presets re-author the rig, not individual shadows:

PresetRig change
verge (default)Canonical rig above
macos_sonomaSofter: hardness 0.2, longer penumbra
material_expressiveStronger key: hardness 0.6
windows_95 / brutalist / terminal_classicLight rig off — bevel/flat per elevation.kmd § R4
neumorphismDual 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)

Referências