diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Changelog for keid-render-basic
 
+## 0.1.9.0
+
+- Moved to geomancy-layout.
+- Added discard and color specializations to `TileMap`.
+
 ## 0.1.8.0
 
 - Embedded texture resources now using KTX2.
diff --git a/keid-render-basic.cabal b/keid-render-basic.cabal
--- a/keid-render-basic.cabal
+++ b/keid-render-basic.cabal
@@ -1,17 +1,17 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.35.1.
+-- This file has been generated from package.yaml by hpack version 0.35.2.
 --
 -- see: https://github.com/sol/hpack
 
 name:           keid-render-basic
-version:        0.1.8.0
+version:        0.1.9.0
 synopsis:       Basic rendering programs for Keid engine.
 category:       Game Engine
 homepage:       https://keid.haskell-game.dev
 author:         IC Rainbow
 maintainer:     keid@aenor.ru
-copyright:      2022 IC Rainbow
+copyright:      2023 IC Rainbow
 license:        BSD3
 license-file:   LICENSE
 build-type:     Simple
@@ -101,59 +101,28 @@
   default-extensions:
       NoImplicitPrelude
       ApplicativeDo
-      BangPatterns
       BinaryLiterals
       BlockArguments
-      ConstrainedClassMethods
-      ConstraintKinds
       DataKinds
       DefaultSignatures
-      DeriveDataTypeable
-      DeriveFunctor
-      DeriveGeneric
-      DeriveLift
-      DeriveTraversable
       DerivingStrategies
       DerivingVia
       DuplicateRecordFields
-      EmptyCase
-      EmptyDataDeriving
-      ExistentialQuantification
-      ExplicitForAll
-      FlexibleContexts
-      FlexibleInstances
       FunctionalDependencies
-      GADTs
-      GeneralizedNewtypeDeriving
       HexFloatLiterals
       ImportQualifiedPost
-      InstanceSigs
-      KindSignatures
       LambdaCase
-      LiberalTypeSynonyms
-      MultiParamTypeClasses
       NamedFieldPuns
-      NamedWildCards
       NumDecimals
-      NumericUnderscores
+      OverloadedRecordDot
       OverloadedStrings
       PatternSynonyms
-      PostfixOperators
-      QuantifiedConstraints
       QuasiQuotes
-      RankNTypes
       RecordWildCards
-      ScopedTypeVariables
-      StandaloneDeriving
-      StandaloneKindSignatures
       StrictData
       TemplateHaskell
-      TupleSections
-      TypeApplications
       TypeFamilies
       TypeOperators
-      TypeSynonymInstances
-      UnicodeSyntax
       ViewPatterns
   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints
   build-depends:
@@ -164,7 +133,9 @@
     , derive-storable-plugin
     , file-embed >=0.0.10
     , geomancy
-    , keid-core >=0.1.8.0
+    , geomancy-layout
+    , gl-block
+    , keid-core >=0.1.9.0
     , keid-geometry
     , neat-interpolation
     , resourcet
@@ -175,4 +146,4 @@
     , vector
     , vulkan
     , vulkan-utils
-  default-language: Haskell2010
+  default-language: GHC2021
diff --git a/src/Engine/UI/Message.hs b/src/Engine/UI/Message.hs
--- a/src/Engine/UI/Message.hs
+++ b/src/Engine/UI/Message.hs
@@ -16,14 +16,16 @@
 import Control.Monad.Trans.Resource (ResourceT)
 import Control.Monad.Trans.Resource qualified as Resource
 import Data.Text qualified as Text
+import Data.Type.Equality (type (~))
 import Data.Vector.Storable qualified as Storable
 import Geomancy (Vec4)
+import Geomancy.Layout.Alignment (Alignment)
+import Geomancy.Layout.Box (Box(..))
 import Geomancy.Vec4 qualified as Vec4
 import UnliftIO.Resource (MonadResource)
 import Vulkan.Core10 qualified as Vk
 
 import Engine.Types qualified as Engine
-import Engine.UI.Layout qualified as Layout
 import Engine.Vulkan.Types (MonadVulkan)
 import Engine.Worker qualified as Worker
 import Render.Font.EvanwSdf.Model qualified as EvanwSdf
@@ -37,7 +39,7 @@
   :: ( MonadResource m
      , MonadUnliftIO m
      , Worker.HasOutput box
-     , Worker.GetOutput box ~ Layout.Box
+     , Worker.GetOutput box ~ Box
      , Worker.HasOutput input
      , Worker.GetOutput input ~ Input
      )
@@ -50,7 +52,7 @@
   :: ( MonadResource m
      , MonadUnliftIO m
      , Worker.HasOutput box
-     , Worker.GetOutput box ~ Layout.Box
+     , Worker.GetOutput box ~ Box
      , Worker.HasOutput source
      )
   => box
@@ -65,7 +67,7 @@
   { inputText         :: Text
   , inputFontId       :: Int32
   , inputFont         :: Font.Container
-  , inputOrigin       :: Layout.Alignment
+  , inputOrigin       :: Alignment
   , inputSize         :: Float
   , inputColor        :: Vec4
   , inputOutline      :: Vec4
@@ -73,8 +75,8 @@
   , inputSmoothing    :: Float
   }
 
-mkAttrs :: Layout.Box -> Input -> Storable.Vector EvanwSdf.InstanceAttrs
-mkAttrs Layout.Box{..} Input{..} =
+mkAttrs :: Box -> Input -> Storable.Vector EvanwSdf.InstanceAttrs
+mkAttrs Box{..} Input{..} =
   if inputSize < 1 then mempty else Storable.fromList do
     Font.PutChar{..} <- chars
     pure EvanwSdf.InstanceAttrs
@@ -89,8 +91,8 @@
       }
   where
     (scale, chars) = Font.putLine
-      boxSize
-      boxPosition
+      size
+      position
       inputOrigin
       inputSize
       inputFont
diff --git a/src/Render/Basic.hs b/src/Render/Basic.hs
--- a/src/Render/Basic.hs
+++ b/src/Render/Basic.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE OverloadedRecordDot #-}
 
 {- |
   All the provided render passes and pipelines packaged and delivered.
@@ -12,6 +13,7 @@
 import Control.Monad.Trans.Resource (ResourceT)
 import Data.Kind (Type)
 import Data.Tagged (Tagged(..))
+import Data.Type.Equality (type (~))
 import RIO.FilePath ((</>), (<.>))
 import RIO.Vector.Partial as Vector (headM)
 import Vulkan.Core10 qualified as Vk
@@ -174,13 +176,28 @@
   => swapchain
   -> RenderPasses
   -> ResourceT (StageRIO st) Pipelines
-allocatePipelines_ swapchain renderpasses = do
+allocatePipelines_ =
+  allocatePipelinesFor Nothing Nothing
+
+allocatePipelinesFor
+  :: ( HasSwapchain swapchain
+     , Foldable textures
+     , Foldable cubes
+     )
+  => textures t
+  -> cubes c
+  -> swapchain
+  -> RenderPasses
+  -> ResourceT (StageRIO st) Pipelines
+allocatePipelinesFor textures cubes swapchain renderpasses = do
   samplers <- Samplers.allocate (Swapchain.getAnisotropy swapchain)
 
   allocatePipelines
-    (Scene.mkBindings samplers Nothing Nothing 0)
+    (Scene.mkBindings samplers textures cubes shadows)
     (Swapchain.getMultisample swapchain)
     renderpasses
+  where
+    shadows = renderpasses.rpShadowPass.smLayerCount
 
 allocatePipelines
   :: Tagged Scene DsLayoutBindings
diff --git a/src/Render/Lit/Colored/Model.hs b/src/Render/Lit/Colored/Model.hs
--- a/src/Render/Lit/Colored/Model.hs
+++ b/src/Render/Lit/Colored/Model.hs
@@ -10,9 +10,9 @@
 import RIO
 
 import Geomancy (Transform, Vec2, Vec4)
-import Geomancy.Gl.Block (Block)
-import Geomancy.Gl.Block qualified as Block
 import Geomancy.Vec3 qualified as Vec3
+import Graphics.Gl.Block (Block)
+import Graphics.Gl.Block qualified as Block
 import Resource.Model qualified as Model
 
 import Engine.Vulkan.Format (HasVkFormat)
diff --git a/src/Render/Lit/Material/Model.hs b/src/Render/Lit/Material/Model.hs
--- a/src/Render/Lit/Material/Model.hs
+++ b/src/Render/Lit/Material/Model.hs
@@ -12,8 +12,8 @@
 
 import Geomancy (Transform, Vec2)
 import Geomancy.Vec3 qualified as Vec3
-import Geomancy.Gl.Block (Block)
-import Geomancy.Gl.Block qualified as Block
+import Graphics.Gl.Block (Block)
+import Graphics.Gl.Block qualified as Block
 
 import Engine.Vulkan.Format (HasVkFormat)
 import Render.Lit.Material (Material)
diff --git a/src/Render/Unlit/Line2d/Model.hs b/src/Render/Unlit/Line2d/Model.hs
--- a/src/Render/Unlit/Line2d/Model.hs
+++ b/src/Render/Unlit/Line2d/Model.hs
@@ -26,6 +26,7 @@
 import RIO
 
 import Control.Monad.Trans.Resource qualified as Resource
+import Data.Type.Equality (type (~))
 import Data.Vector.Generic qualified as Generic
 import Foreign.Storable.Generic (GStorable)
 import Geomancy (Vec2, Vec4, vec3)
diff --git a/src/Render/Unlit/Textured/Model.hs b/src/Render/Unlit/Textured/Model.hs
--- a/src/Render/Unlit/Textured/Model.hs
+++ b/src/Render/Unlit/Textured/Model.hs
@@ -26,12 +26,12 @@
 
 import Geomancy (Transform, Vec2, Vec4)
 import Geomancy.Vec3 qualified as Vec3
+import Graphics.Gl.Block (Block)
+import Graphics.Gl.Block qualified as Block
 import RIO.Vector.Storable qualified as Storable
 import Vulkan.Core10 qualified as Vk
 import Vulkan.NamedType ((:::))
 import Vulkan.Zero (Zero(..))
-import Geomancy.Gl.Block (Block)
-import Geomancy.Gl.Block qualified as Block
 
 import Engine.Types (HKD)
 import Engine.Vulkan.Format (HasVkFormat(..))
diff --git a/src/Render/Unlit/TileMap/Code.hs b/src/Render/Unlit/TileMap/Code.hs
--- a/src/Render/Unlit/TileMap/Code.hs
+++ b/src/Render/Unlit/TileMap/Code.hs
@@ -87,7 +87,8 @@
     int repeatTiles      = fTextureIds[3];
 
     // TODO
-    // const vec4 fTextureGamma = vec4(1.0);
+    layout(constant_id=0) const float alphaThreshold = 0.0;
+    layout(constant_id=1) const bool postMultiply = true;
 
     void main() {
       if (repeatTiles == 0 && (fTexCoord.x < 0.0 || fTexCoord.y < 0.0 || fTexCoord.x > 1.0 || fTexCoord.y > 1.0)) {
@@ -132,6 +133,12 @@
         spriteUV,
         0
       );
+
+      if (oColor.a <= alphaThreshold)
+        discard;
+
+      if (postMultiply)
+        oColor.rgb *= oColor.a;
     }
   |]
   where
diff --git a/src/Resource/Font/EvanW.hs b/src/Resource/Font/EvanW.hs
--- a/src/Resource/Font/EvanW.hs
+++ b/src/Resource/Font/EvanW.hs
@@ -20,12 +20,15 @@
 import Data.Aeson (FromJSON, eitherDecodeStrict')
 import Foreign qualified
 import Geomancy (Vec2, vec2, pattern WithVec2)
+import Geomancy.Layout qualified as Layout
+import Geomancy.Layout.Alignment (Alignment)
+import Geomancy.Layout.Box (Box(..))
+import Geomancy.Layout.Box qualified as Box
 import GHC.Stack (withFrozenCallStack)
 import RIO.HashMap qualified as HashMap
 import RIO.Text qualified as Text
 import Vulkan.NamedType ((:::))
 
-import Engine.UI.Layout qualified as Layout
 import Resource.Source (Source)
 import Resource.Source qualified as Source
 
@@ -106,14 +109,19 @@
 putLine
   :: "WH"        ::: Vec2
   -> "XY"        ::: Vec2
-  -> "Alignment" ::: Layout.Alignment
+  -> "Alignment" ::: Alignment
   -> "Size"      ::: Float
   -> "Font"      ::: Container
   -> "Line"      ::: [Char]
   -> ("scale" ::: Float, [PutChar])
-putLine (WithVec2 cw ch) (WithVec2 cx cy) Layout.Alignment{..} targetSize font =
-  (sizeScale,) . extract . foldl' step (0, 0, [])
+putLine bs bp a targetSize font =
+  (sizeScale,) . extract . foldl' step (0, [])
   where
+    parent = Box
+      { position = bp
+      , size = bs
+      }
+
     Container
       { size   = fontSize
       , width  = atlasWidth
@@ -122,48 +130,46 @@
       } = font
 
     sizeScale = targetSize / fontSize
+    baseline = 0.7125
 
-    extract (offX, _offY, bits) = do
-      (WithVec2 w h, WithVec2 x y, (offset, scale)) <- bits
-      let
-        ax = case alignX of
-          Layout.Begin  -> -cw / 2
-          Layout.Middle -> -offX * sizeScale / 2
-          Layout.End    -> cw / 2 - offX * sizeScale
+    extract (offX, bits) =
+      Box.withTRBL (Layout.placeSize a (vec2 (offX * sizeScale) targetSize) parent) \t _r _b l -> do
+        (WithVec2 w h, WithVec2 x y, (pcOffset, pcScale)) <- bits
+        let
+          pcPos =
+            vec2
+              (l + x * sizeScale)
+              (t + y * sizeScale + baseline * targetSize)
 
-        ay = case alignY of
-          Layout.Begin  -> -ch / 2 + targetSize * 1.3
-          Layout.Middle -> targetSize * 0.5
-          Layout.End    -> ch / 2 - targetSize * 0.5
+          pcSize =
+            vec2
+              (w * sizeScale)
+              (h * sizeScale)
 
-      pure PutChar
-        { pcPos    = vec2 (cx + ax + x * sizeScale) (cy + ay + y * sizeScale)
-        , pcSize   = vec2 (w * sizeScale) (h * sizeScale)
-        , pcOffset = offset
-        , pcScale  = scale
-        }
+        pure PutChar{..}
 
-    step (offX, offY, acc) ' ' =
-      ( offX + fontSize / 2
-      , offY
-      , acc
-      )
+    step (offX, acc) = \case
+      ' ' ->
+        ( offX + fontSize / 2
+        , acc
+        )
 
-    step (offX, offY, acc) char =
-      case HashMap.lookup char characters <|> HashMap.lookup '?' characters of
-        Nothing ->
-          (offX, offY, acc)
-        Just Character{..} ->
-          ( offX + advance
-          , offY
-          , ( vec2 width (-height)
-            , vec2 ox oy
-            , (uvOffset, uvScale)
-            ) : acc
-          )
-          where
-            ox = offX + width / 2 - originX
-            oy = offY + height / 2 - originY
+      char ->
+        case HashMap.lookup char characters of
+          Nothing ->
+            ( offX
+            , acc
+            )
+          Just Character{..} ->
+            ( offX + advance
+            , ( vec2 width (-height)
+              , vec2 ox oy
+              , (uvOffset, uvScale)
+              ) : acc
+            )
+            where
+              ox = width / 2 - originX + offX
+              oy = height / 2 - originY
 
-            uvOffset = vec2 (x / atlasWidth) (y / atlasHeight)
-            uvScale  = vec2 (width / atlasWidth) (height / atlasHeight)
+              uvOffset = vec2 (x / atlasWidth) (y / atlasHeight)
+              uvScale  = vec2 (width / atlasWidth) (height / atlasHeight)
diff --git a/src/Stage/Loader/Scene.hs b/src/Stage/Loader/Scene.hs
--- a/src/Stage/Loader/Scene.hs
+++ b/src/Stage/Loader/Scene.hs
@@ -5,6 +5,7 @@
 
 import RIO
 
+import Data.Type.Equality (type (~))
 import UnliftIO.Resource (MonadResource)
 
 import Engine.Camera qualified as Camera
diff --git a/src/Stage/Loader/Setup.hs b/src/Stage/Loader/Setup.hs
--- a/src/Stage/Loader/Setup.hs
+++ b/src/Stage/Loader/Setup.hs
@@ -13,7 +13,6 @@
 import Engine.StageSwitch (trySwitchStage)
 import Engine.Types (StackStage(..), StageSetupRIO)
 import Engine.Types qualified as Engine
-import Engine.UI.Layout qualified as Layout
 import Engine.UI.Message qualified as Message
 import Engine.Vulkan.Swapchain qualified as Swapchain
 import Engine.Vulkan.Types (Queues)
@@ -161,10 +160,8 @@
     rsQuadUV <- Model.createStagedL (Just "rsQuadUV") pools (Quad.toVertices Quad.texturedQuad) Nothing
     Model.registerIndexed_ rsQuadUV
 
-    screenBoxP <- Layout.trackScreen
-
     rsUI <- Region.local $
-      UI.spawn pools screenBoxP uiSettings
+      UI.spawn pools uiSettings
 
     let
       updateProgress text = do
diff --git a/src/Stage/Loader/UI.hs b/src/Stage/Loader/UI.hs
--- a/src/Stage/Loader/UI.hs
+++ b/src/Stage/Loader/UI.hs
@@ -14,11 +14,14 @@
 import Control.Monad.Trans.Resource (ResourceT)
 import Control.Monad.Trans.Resource qualified as Resource
 import Geomancy (vec4)
+import Geomancy.Layout qualified as Layout
+import Geomancy.Layout.Alignment qualified as Alignment
+import Geomancy.Layout.Box qualified as Box
 import Geomancy.Transform qualified as Transform
 import Vulkan.Core10 qualified as Vk
 
+import Engine.Camera qualified as Camera
 import Engine.Types qualified as Engine
-import Engine.UI.Layout qualified as Layout
 import Engine.UI.Message qualified as Message
 import Engine.Vulkan.Types (HasVulkan, Queues)
 import Engine.Worker qualified as Worker
@@ -60,32 +63,48 @@
 
 spawn
   :: Queues Vk.CommandPool
-  -> Layout.BoxProcess
   -> Settings fonts textures
   -> Engine.StageRIO env (Resource.ReleaseKey, UI)
-spawn pools screenBoxP Settings{..} = Region.run do
-  paddingVar <- Worker.newVar 16
-  screenPaddedP <- Layout.padAbs screenBoxP paddingVar
+spawn pools Settings{..} = Region.run do
+  screenBoxP <- Camera.trackOrthoPixelsCentered
 
-  -- Messages
+  screenPaddedP <-
+    Worker.spawnMerge1
+      (Box.resize (-16))
+      screenBoxP
 
-  sections <- Layout.splitsRelStatic Layout.sharePadsV screenPaddedP [2, 1, 1]
+  -- Messages
 
-  (titleBoxP, subtitleBoxP, progressBoxP) <- case sections of
-    [titleBoxP, subtitleBoxP, progressBoxP] ->
-      pure (titleBoxP, subtitleBoxP, progressBoxP)
-    _ ->
-      error "assert: shares in Traversable preserve structure"
+  sectionsP <-
+    Worker.spawnMerge1
+      ( \parent ->
+          let
+            (top, bottom) = Layout.vertical (Right 0.5) parent
+          in
+            ( top
+            , Layout.vertical (Right 0.5) bottom
+            )
+      )
+      screenPaddedP
 
+  titleBoxP <- Worker.spawnMerge1 fst sectionsP
   titleP <- Worker.newVar title >>= Message.spawn titleBoxP
+
+  lowerSectionP <- Worker.spawnMerge1 snd sectionsP
+
+  subtitleBoxP <- Worker.spawnMerge1 fst lowerSectionP
   subtitleP <- Worker.newVar subtitle >>= Message.spawn subtitleBoxP
 
   progressInput <- Worker.newVar progress
+  progressBoxP <- Worker.spawnMerge1 snd lowerSectionP
   progressP <- Message.spawn progressBoxP progressInput
 
   -- Splash
 
-  backgroundBoxP <- Layout.fitPlaceAbs Layout.Center 1.0 screenBoxP
+  backgroundBoxP <-
+    Worker.spawnMerge1
+      (Layout.placeAspect Alignment.center 1)
+      screenBoxP
 
   backgroundP <-
     Worker.spawnMerge1
@@ -93,7 +112,7 @@
           UnlitTextured.stores1
               (Samplers.linear Samplers.indices)
               backgroundIx
-              [Layout.boxTransformAbs box]
+              [Box.mkTransform box]
       )
       backgroundBoxP
 
@@ -106,7 +125,7 @@
 
   spinnerTransformBoxP <-
     Worker.spawnMerge1
-      (Layout.boxTransformAbs . snd . Layout.boxFitScale 64)
+      (Box.mkTransform) -- FIXME: . Layout.boxFitScale 64)
       subtitleBoxP
 
   spinnerP <-
@@ -133,7 +152,7 @@
   where
     title = Message.Input
       { inputText         = titleMessage
-      , inputOrigin       = Layout.CenterBottom
+      , inputOrigin       = Alignment.centerBottom
       , inputSize         = 64
       , inputColor        = vec4 1 0.5 0.25 1
       , inputFont         = largeFont fonts
@@ -145,7 +164,7 @@
 
     subtitle = Message.Input
       { inputText         = "Loading..."
-      , inputOrigin       = Layout.CenterTop
+      , inputOrigin       = Alignment.centerTop
       , inputSize         = 32
       , inputColor        = vec4 0.5 1 0.25 1
       , inputFont         = smallFont fonts
@@ -157,7 +176,7 @@
 
     progress = Message.Input
       { inputText         = ""
-      , inputOrigin       = Layout.CenterBottom
+      , inputOrigin       = Alignment.centerBottom
       , inputSize         = 16
       , inputColor        = vec4 0.25 0.5 1 1
       , inputFont         = smallFont fonts
