diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..e2c8789ac6e39a487548191e18aa7170ed99693b
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,33 @@
+[flake8]
+doctests = True
+exclude = .git, .eggs, __pycache__, docs, dist, venv, .tox, env, envs, .cache
+ignore =
+    # line too long, we use black
+    E501,
+    # blank line contains whitespace / final whitespace, we use black
+    W291,
+    W293,
+    # blank line contains whitespace / final whitespace, we use black
+    E303,
+    # line break before binary operator
+    W503,
+    # wrong flake defaults: see https://github.com/psf/black/issues/315, https://github.com/psf/black/issues/43
+    E203,
+    W503,
+    W504
+extend-ignore =
+    RST307,
+    # Google Python style is not RST until after processed by Napoleon
+    # See https://github.com/peterjc/flake8-rst-docstrings/issues/17
+    RST201,RST203,RST301,
+rst-roles =
+    class,
+    func,
+    ref,
+rst-directives =
+    envvar,
+    exception,
+rst-substitutions =
+    version,
+per-file-ignores =
+    __init__.py:F401,E402
diff --git a/.gitignore b/.gitignore
index b99139dcfcfbc2e619b256f64641e402c7f1a14c..4d3531412364513db77ce3b1683e14c64de0fb1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ __pycache__/
 # Distribution / packaging
 .Python
 env/
+envs/
 build/
 develop-eggs/
 dist/
@@ -69,6 +70,7 @@ instance/
 
 # Sphinx documentation
 docs/_build/
+docs/lib/reference/_autosummary/
 
 # PyBuilder
 target/
@@ -95,6 +97,14 @@ ENV/
 # vim files
 *.sw*
 
+# VisualStudioCode
+.vscode/*      # Maybe .vscode/**/* instead - see comments
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+**/.history
+
 # folder
 tmp/
 .idea
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a8433061ec7d4ef8dd146584b50b4a25e9998d7d..27339812d00dab517cfb11b823c3ea7b03ce1d66 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,9 +1,12 @@
-image: python:latest
+image: python:3.10.10
 
-# Change pip's cache directory to be inside the project directory since we can
-# only cache local items.
 variables:
+  # Change pip's cache directory to be inside the project directory since we can
+  # only cache local items.
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
+  # If you do not have access to e.g. databases in the ci, use pytest.mark.skipif to skip a test:
+  #   @pytest.mark.skipif(os.environ.get('TEST_DB') != 'Dynamo')
+  CI_RUNNING: "true"
 
 # Pip's cache doesn't store the python packages
 # https://pip.pypa.io/en/stable/reference/pip_install/#caching
@@ -11,31 +14,32 @@ variables:
 # If you want to also cache the installed packages, you have to install
 # them in a virtualenv and cache it as well.
 cache:
+  key: one-key-to-rule-them-all
   paths:
     - .cache/pip
-    - venv/
     - .cache/apt
+    - .cache/virtualenvs
 
 stages:
   - build
   - test
   - deploy
 
+#commands to run in the Docker container before starting each job
 before_script:
-  - pip install virtualenv
-  - virtualenv venv
-  - source venv/bin/activate
+  - pip install poetry
+  - poetry config cache-dir $(pwd)/.cache
+  - mkdir -p .cache/apt
+  - apt-get update -yqq
+  - apt-get -o dir::cache::archives=".cache/apt" install -y -qq gfortran libopenblas-dev liblapack-dev
+  - apt-get -o dir::cache::archives=".cache/apt" install -y -qq libgmp-dev
+  - poetry install --with docs # --extras extra we require
+  - source $(poetry env info -p)/bin/activate
 
 dist:
   stage: build
   script:
-    - mkdir -p .cache/apt
-    - apt-get update -yqq
-    - apt-get install -y gfortran libopenblas-dev liblapack-dev
-    - apt-get -o dir::cache::archives=".cache/apt" install -y -qq gfortran liblapack-dev libgmp-dev
-    - python setup.py bdist_wheel
-    # an alternative approach is to install and run:
-    - pip install dist/*
+    - poetry build
   artifacts:
     paths:
       - dist/*.whl
@@ -46,15 +50,13 @@ dist:
 pages:
   stage: build
   script:
-    - pip install sphinx sphinx_rtd_theme recommonmark
-    - cd docs
-    - make html
-    - cd ..
+    - sphinx-build -M html docs docs/_build/ -E -a -j auto --keep-going
     - mkdir -p public
     - rm -rf public/*
     - mv docs/_build/html/* public/  # add it to pages. Pages is exposing public/index.html
   only:
     - master
+    - tags
   cache:
     paths:
       - public
@@ -65,44 +67,29 @@ pages:
 
 lint:
   stage: test
-  before_script:
-    - pip install -q flake8
   script:
     - flake8
 
 test:
   stage: test
   script:
-    - pip --version
-    - pip install tox  # you can also use tox
-    - tox
-  coverage: '/^TOTAL.+?(\d+\%)$/'
+    - coverage run -m pytest
+    - coverage report
+    - coverage xml
+    - mkdir -p report
+    - rm -rf report/*
+    - mv coverage.xml report/
+  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
   artifacts:
-    # paths:
-    # pa- report/unit
     reports:
-      junit:
-        - report/junit.xml
+      coverage_report:
+        coverage_format: cobertura
+        path: report/coverage.xml
 
 pypi:
-  image: docker.km3net.de/base/python:3
   stage: deploy
-  cache: {}
-  before_script:
-    - echo "Starting upload to pypi"
   script:
-    # Check if current_version is already uploaded
-    - VERSION=$((python -c "import configparser; config = configparser.ConfigParser(); config.read('setup.cfg'); print(config['bumpversion']['current_version'])") 2>&1)
-    - MODULE_NAME=$((python -c "import configparser; config = configparser.ConfigParser(); config.read('setup.cfg'); print(config['metadata']['name'])") 2>&1)
-    - PACKAGE_JSON_URL="https://pypi.org/pypi/$MODULE_NAME/json" 
-    - apt-get install -qq -y jq
-    - PYPI_VERSIONS=$(curl -s "$PACKAGE_JSON_URL" | jq  -r '.releases | keys | .[]' | sort -V)
-    - if [[ $PYPI_VERSIONS =~ $VERSION ]]; then echo "Version $VERSION is already uploaded!"; exit 1; fi
-    # Version not already uploaded so do it now.
-    - echo "Uploading version $VERSION"
-    - pip install -U twine
-    - python setup.py sdist
-    - twine upload dist/*
+    - poetry publish -u $PYPI_USERNAME -p $PYPI_PASSWORD
   rules:
-    # for debuggin: git commit -am "deb" && git push && bumpversion patch && git tag -l --sort=-v:refname | head -n 1 | git push origin
+    # for debugging: git commit -am "deb" && git push && bumpversion patch && git tag -l --sort=-v:refname | head -n 1 | git push origin
     - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b6d2326ebedc61e8324b46226aabeefae75dce69..ea2002bd234d1469569e462f57e8db0433cba5b8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,21 +1,26 @@
 default_language_version:
     python: python3
+default_install_hook_types:
+-   pre-commit
+-   commit-msg
 repos:
 -   repo: https://github.com/ambv/black
-    rev: 22.3.0
+    rev: 23.1.0
     hooks:
     - id: black
-      language_version: python3.10
--   repo: https://gitlab.com/pycqa/flake8
+      fail_fast: true
+    - id: black-jupyter
+-   repo: https://github.com/pycqa/flake8
     rev: 3.9.2
     hooks:
     - id: flake8
+      fail_fast: true
 -   repo: https://github.com/pre-commit/pre-commit-hooks 
-    rev: v4.0.1
+    rev: v4.3.0
     hooks:
     - id: check-json
     - id: pretty-format-json
-      args: ['--indent', '4']
+      args: ['--indent', '4', '--autofix']
     - id: check-merge-conflict
     - id: check-toml
     - id: check-yaml
@@ -27,3 +32,17 @@ repos:
     hooks:
     - id: blacken-docs
       additional_dependencies: [black==22.3.0]
+-   repo: https://github.com/compilerla/conventional-pre-commit
+    rev: v2.1.1
+    hooks:
+      - id: conventional-pre-commit
+        fail_fast: true
+        stages: [commit-msg]
+        args: [release, feat, fix, ci, docs, style, refactor, build, chore, test, perf, revert]
+- repo: https://github.com/python-poetry/poetry
+  rev: 1.4.2
+  hooks:
+    - id: poetry-check
+    - id: poetry-lock
+      stages: [commit-msg]
+      args: ["--no-update"]
diff --git a/.readthedocs.yml b/.readthedocs.yml
deleted file mode 100644
index 1db793f37946026137fbe27e56e648887339190c..0000000000000000000000000000000000000000
--- a/.readthedocs.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-# .readthedocs.yml
-# Read the Docs configuration file
-# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
-
-# Required
-version: 2
-
-
-formats: all
-
-build:
-  image: latest
-
-# Build documentation in the docs/ directory with Sphinx
-sphinx:
-  configuration: docs/conf.py
-
-# Optionally set the version of Python and requirements required to build your docs
-python:
-  version: 3.7
-  install:
-    - method: pip
-      path: .
-      extra_requirements:
-        - docs
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 1b9944a17a1e271668c17b9beab1cb17983a652a..03943a93bcb1ad219803832dd7699b1503d8b7f3 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,166 +1,303 @@
-.. highlight:: shell
-
-============
 Contributing
 ============
+..
+    [contributing]
 
-Contributions are welcome, and they are greatly appreciated! Every little bit
-helps, and credit will always be given.
+.. highlight:: shell
 
-You can contribute in many ways:
+Thank you for your interest in contributing to the project!
+Your contributions are greatly appreciated, and every little bit helps to make the project better for everyone.
 
 Types of Contributions
 ----------------------
 
+There are many ways you can contribute to the project, including:
+
+.. toctree::
+   :maxdepth: 2
+   
+   bugs_report
+   bugs_fix
+   feat
+   docu
+   tests
+   feedback
+   
+.. _bugs_report:
+
 Report Bugs
 ~~~~~~~~~~~
 
-Report bugs at https://gitlab.mpcdf.mpg.de/dboe/tfields/issues.
+If you encounter a bug in the project, please report it by `creating an issue on the project's issue tracker <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/issues/>`_.
+When reporting a bug, please include:
+
+- Your operating system name and version
+- Any details about your local setup that might be helpful in troubleshooting
+- Detailed steps to reproduce the bug
+
+If you want quick feedback, it is helpful to mention specific developers
+(@developer_name) or @all. This will trigger a mail to the corresponding developer(s).
+
+.. _bugs_fix:
+
+Fixing Bugs
+~~~~~~~~~~~
+
+If you would like to help fix a bug in the project, take a look at the issues tagged as "bug" and "help wanted" in the project's issue tracker.
+These issues are open to anyone who wants to help fix them.
 
-If you are reporting a bug, please include:
+.. _feat:
 
-* Your operating system name and version.
-* Any details about your local setup that might be helpful in troubleshooting.
-* Detailed steps to reproduce the bug.
+Implementing Features
+~~~~~~~~~~~~~~~~~~~~~
 
-If you want quick feedback, it is helpful to mention speicific developers
-(@devloper_name) or @all. This will trigger a mail to the corresponding developer(s).
+If you would like to help implement a new feature in the project, take a look at the issues tagged as "enhancement" and "help wanted" in the project's issue tracker.
+These issues are open to anyone who wants to help implement them.
 
-Fix Bugs
-~~~~~~~~
+.. _docu:
 
-Look through the repository issues for bugs. Anything tagged with "bug" and "help
-wanted" is open to whoever wants to implement it.
+Writing Documentation
+~~~~~~~~~~~~~~~~~~~~~
 
-Implement Features
-~~~~~~~~~~~~~~~~~~
+Good documentation is key to a successful project.
+If you would like to help improve the project's documentation, you can contribute by:
 
-Look through the remote issues for features. Anything tagged with "enhancement"
-and "help wanted" is open to whoever wants to implement it.
+- Adding documentation to the project's code
+- Writing new documentation pages or articles
+- Improving existing documentation pages or articles
 
-Write Documentation
-~~~~~~~~~~~~~~~~~~~
+.. _tests:
 
-`tfields` could always use more :ref:`documentation<Documentation>`, whether as part of the
-official `tfields` docs, in docstrings, or even on the web in blog posts,
-articles, and such.
+Writing Tests
+~~~~~~~~~~~~~
 
-Write Unittests or Doctests
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+`tfields` profits a lot from better :ref name="Testing":`testing`. We encourage you to add unittests 
+with the `pytest` module (`unittest` module is OK, too) in the `tests` directory or doctests (as part of docstrings or in the documentation).
 
-`tfields` profits a lot from better :ref:`testing<Testing>`. We encourage you to add unittests 
-(in the `tests` directory) or doctests (as part of docstrings or in the documentation).
+.. _feedback:
 
 Submit Feedback
 ~~~~~~~~~~~~~~~
 
-The best way to send feedback is to file an `Issue <https://gitlab.mpcdf.mpg.de/dboe/tfields/issues>`_.
+The best way to send feedback is to file an `Issue <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/issues/>`_.
 
 If you are proposing a feature:
 
 * Explain in detail how it would work.
 * Keep the scope as narrow as possible, to make it easier to implement.
 * Remember that this is a volunteer-driven project, and that contributions
-  are welcome :)
+  are welcome
 
-Get Started!
-------------
+Setup and Install
+-----------------
 
-Ready to contribute? Here's how to set up `tfields` for local development.
+Ready to contribute?
+Here's how to set up `tfields` for local development.
 
-1. Fork the `tfields` repo.
-2. Clone your fork locally::
+1. If you are part of the project team, you are welcome to submit changes through your own development branch in the main repository.
+   However, if you are not part of the team, we recommend `forking <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/forks/new/>`_ the project and submitting changes through a pull request from your forked repository.
+   This allows us to review and discuss your changes before merging them into the main project.
+2. Clone the repository to your local machine::
 
-    $ git clone git@gitlab.mpcdf.mpg.de:dboe/tfields.git
+    .. code-block:: shell
 
-3. Set up your fork for local development::
+        git clone git@gitlab.mpcdf.mpg.de:dboe/tfields.git
 
-    $ cd tfields/
-    $ pip install .[dev]
+    Change adequately if you forked.
 
-4. Step 3. already installed `pre-commit <https://pre-commit.com/>`_. Initialize it by running::
+3. Set up your the local development environment::
 
-    $ pre-commit install
+    .. code-block:: shell
+        
+        cd tfields/
+        make env
+    
+    `make env` will setup a virtual conda environment for you, install all packages there and install `pre-commit <https://pre-commit.com/>`_.
+    
+The above will only be necessary once.
 
-5. Create a branch for local development::
+You are now ready to go.
+Have a look at 
+    
+    .. code-block:: shell
 
-    $ git checkout -b name-of-your-bugfix-or-feature
+        make help
+
+to get an overview over the daily operations that are automatized for you.
+
+Daily Operations
+----------------
+
+- Work on the virtual environment (created by `make env`) by activating it::
+
+    .. code-block:: shell
+
+        conda activate ./env
+
+- Work on a branch different from `master/main` for local development::
+
+    .. code-block:: shell
+
+        git checkout -b <name-of-your-bugfix-or-feature>
 
    Now you can make your changes locally.
 
-6. When you're done making changes, check that your changes pass flake8 and the
-   tests::
+- When you're done making changes, check that these pass the linters and tests::
+
+    .. code-block:: shell
 
-    $ make test
+        make test
+    
+- Commit your changes and push your branch to origin::
 
-7. Commit your changes and push your branch to origin::
+    .. code-block:: shell
 
-    $ git add .
-    $ git commit -m "Your detailed description of your changes."
-    $ git push origin name-of-your-bugfix-or-feature
+        git add <files to be added to your commit>
+        git commit -m "Your detailed description of your changes."
+        git push origin name-of-your-bugfix-or-feature
 
-8. Submit a pull request through the repository website.
+- Submit a `merge request <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests/>`_ through the repository website Follow the `Guidelines <#guidelines>` below.
+  
+.. _guidelines:
 
-Pull Request Guidelines
------------------------
+Code Review and Merge Request Guidelines
+----------------------------------------
 
-Before you submit a pull request, check that it meets these guidelines:
+All code contributions to the project are reviewed by project maintainers before they are merged.
+To ensure that your code contribution is accepted, please follow these guidelines:
 
-1. The pull request should include tests.
-2. If the pull request adds functionality, the docs should be updated. Put
-   your new functionality into a function with a docstring, and add the
-   feature to the list in README.rst.
-3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check
-   https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests
+Before you submit a `merge request <https://gitlab.mpcdf.mpg.de/dboe/tfields/-/merge_requests/>`_, check that you follow these guidelines:
+
+1. Write code that is well-documented and easy to understand.
+2. Write tests that verify the behavior of your code.
+    The merge request should work for all supported python versions and operation systems.
+3. Use the project's coding style guidelines.
+4. Submit a pull request that describes your changes and why they are valuable to the project.
+5. Respond to feedback from code reviewers in a timely manner.
    and make sure that the tests pass for all supported Python versions.
 
 Testing
 -------
 
-To run tests, use::
+To run tests, use
+
+    .. code-block:: shell
 
-    $ make test
+        make test
 
-To run a subset of tests, you have the following options::
+To run a subset of tests, you have the following options:
 
-    $ pytest tests/test_package.py
+- You can run a subset of tests by specifying a test file or test name
 
-    $ pytest tests/test_package.py::Test_tfields::test_version_type
+    .. code-block:: shell
 
-    $ pytest --doctest-modules docs/usage.rst
+        pytest tests/test_package.py
 
-    $ pytest --doctest-modules tfields/core.py -k "MyClass.funciton_with_doctest"
+        pytest tests/test_package.py::Test_tfields::test_version_type
+    
+- To test doctests, in docstrings or documentation, use the flag `--doctest-modules` (active by default via pyproject.toml)
 
-Use the '--trace' option to directly jump into a pdb debugger on fails. Check out the coverage of your api with::
+    .. code-block:: shell
 
-    $ make coverage
+        pytest --doctest-modules docs/usage.rst
+
+        pytest --doctest-modules tfields/core.py -k "MyClass.function_with_doctest"
+
+- Use the `--trace` option to directly jump into a pdb debugger on fails.
+
+To run tests with code coverage, use the command
+
+    .. code-block:: shell
+
+        make coverage
+
+This will generate an HTML report showing the coverage of each file in the project.
 
 Documentation
 -------------
-To compile the documentation (including automatically generated module api docs), run::
+To compile the documentation (including automatically generated module api docs), run
 
-    $ make doc
+    .. code-block:: shell
+
+        make docs
+    
+To build the documentation and run a live-reloading server, use the command 
+
+    .. code-block:: shell
+
+        make docs-serve
+
+This will start a server that will rebuild the documentation whenever a change is made, and display it in your web browser.
 
 Use doctests as much as possible in order to have tested examples in your documentation.
 
-Styleguide
+Style Guide
 -----------
+
 Please follow the `google style guide <https://google.github.io/styleguide/pyguide.html>`_ illustrated
 by `this example <https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html>`_.
 
+Additionaly we format *args and **kwargs like so:
+
+    .. code-block:: python
+
+        def fun(param1: int, param2: float, *args, kw1: str = None, **kwargs)
+            """
+            Args:
+                param1 (int): The first parameter.
+                param2 (float): description
+                *args (str): description
+                    Second line. List follow now
+                
+                    0: first position is used as ...
+                    1: second position is used as ...
+                    
+                    further are passed to/consumed by/used for ...
+
+                kw1 (Optional[str]): description. Defaults to None.
+                    Second line of description should be indented.
+                **kwargs: description
+
+                    **sub_key1 (Optional[str]): This key is retrieved by kwargs.pop('sub_key1', Default)
+                    **sub_key2 (Optional[str]): This key is retrieved by kwargs.pop('sub_key1', Default)
+                    
+                    further are passed to/consumed by/used for ...
+
+                ...
+            """
+
+
 Deploying
 ---------
 
 A reminder for the maintainers on how to deploy.
 Make sure all your changes are committed.
-Then run::
+Then run
 
-    $ bump2version patch # possible: major / minor / patch
-    $ git push
-    $ git push --tags
+    .. code-block:: shell
 
-or use the convenient alias for the above (patch increases only)::
+        make publish
 
-    $ make publish
+.. dropdown:: Failed CI deploy stage
+    :icon: alert
+    
+    In case of failure in the CI deploy stage after :code:`make publish`, use the following rule to delete the tag (also remote)
+
+    .. code-block:: shell
+    
+        make untag
+    
+If something fails on the ci side, you can make use of 
 
 The CI will then deploy to PyPI if tests pass.
+
+TODO Notes
+----------
+
+If you add a `TODO` note to the code, please follow these conventions:
+
+- Use the format `TODO(@responsible-user): note` to assign responsibility for the note.
+- Place the `TODO` note in a docstring near the relevant code.
+- Use priority labels like `TODO-0`, `TODO-1`, and `TODO-2` to indicate the urgency of the note.
+
+We appreciate your contributions to the project and look forward to working with you!
diff --git a/LICENSE.rst b/LICENSE.rst
index 65acff406d68f658e56df3ee039b1d7c96403cf2..2854fc17c17e5a4e5b16e10a40957009b2e9c0c8 100644
--- a/LICENSE.rst
+++ b/LICENSE.rst
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2022, Daniel Böckenhoff
+Copyright (c) 2023, Daniel Böckenhoff
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
index 88b74aa9e682a2e35f8d0e1d42b27203d27599a7..af80cf9e78ae1ff893a2b7a01a1de0d1ccff12a2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,69 +1,114 @@
-SHELL := /bin/bash # Use bash syntax
-CURRENT_PATH := $(shell pwd)
-MODULE := $(shell basename "$(CURRENT_PATH)")
-VERSION := $(shell python -c "import sys; import $(MODULE); sys.stdout.write($(MODULE).__version__)")
-SOURCES := $(shell find $(MODULE) -name '*.py')
-DOCUMENTATION := $(shell find . -name '*.rst')
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SPHINXSOURCEDIR = ./docs
-SPHINXBUILDDIR  = docs/_build
-GITSTATUS = $(shell git status --porcelain)
+include tools/Makefile.envs
+
+# OPTIONS
 part ?= patch
 
-test: FORCE
+.PHONY: info
+## Show useful debugging information regarding git status, log, and remote config
+info:
+	@printf $(_INFO) "Package" "$(MODULE) v$(VERSION)" ;
+	@echo ""
+	@printf $(_INFO) "Git remote" "" ; \
+	git remote -v
+	@echo ""
+	@printf $(_INFO) "Git log" "" ; \
+	git log -8 --graph --oneline --decorate --all
+	@echo ""
+	@printf $(_INFO) "Git status" "" ; \
+	git status
+	
+.PHONY: env-info
+## Show useful debugging information regarding the environment
+env-info:
+	$(CONDA_EXE) config --get
+	$(CONDA_EXE) info
+	$(CONDA_EXE) list >> $(DEBUG_FILE)
+
+## Lint with flake8 only
+lint-flake:
 	flake8 tfields tests
+
+## Lint with pylint only
+lint-pylint:
 	pylint tfields tests
+	
+## Lint your code
+lint: lint-flake lint-pylint
+	
+## Run your unit tests
+test:
 	py.test
 
-coverage:
-	# coverage run $(MODULE) test
+## Build the coverage
+coverage-build:
 	py.test --cov=$(MODULE) || true
-	# coverage report
 	coverage html
+
+## Build the coverage html and display it
+coverage: coverage-build
 	python -m webbrowser htmlcov/index.html
 
-clean:
+## Remove pre-commit artifacts
+clean-pre-commit:
+	pre-commit clean
+
+## Clean the documentation artifacts
+clean-docs:
+	rm -rf docs/_build
+
+.PHONY: clean
+## Clean your project from unnecessary artifacts
+clean: clean-docs
+	rm -f .make.*
 	coverage erase
 	rm -rf htmlcov
-	rm -rf docs/_build
-	rm -rf docs/source
+	rm -rf docs/lib/reference/_autosummary/
 	rm -rf dist
 	rm -rf build
 	rm -rf report
 	rm -rf .tox
 	rm -rf .pytest_cache
 	rm -rf *.egg-info
+	find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete
 	# pre-commit clean
 
-publish:
-	# call optional with argument: make part=minor publish
-	bump2version $(part)  # possible: major / minor / patch
-	git push
-	git push --tags
+## Export the environment to a requirements.txt file
+requirements: pyproject.toml
+	poetry export --without-hashes --format=requirements.txt > requirements.txt
+	
+## Install docs requirements using poetry
+install-docs:
+ifeq ($(ENVACTIVE),true)
+ifeq ($(DOCS_INSTALLED),false)
+	${CONDA_RUN}poetry install --with docs
+endif
+else
+	@printf $(_WARN) "docs" "CONDA environment is not active. Make sure to activate the environment ($$ make env-activate) or install the requirements manually ($$ poetry install --with docs). The successful build of the following command is your responsibility."
+endif
 
-untag:
-	# remove last tag. mostly, because publishing failed
-	git tag -d v$(VERSION)
-	git push origin :refs/tags/v$(VERSION)
-
-requirements: setup.cfg
-	# We have all the information in the setup.cfg file. For some reasons (e.g. bug in setuptools or limitations to use setup.cfg in readthedocs) we still need a requirements file
-	python -c "import configparser; import os; config = configparser.ConfigParser(); config.read('setup.cfg'); deps = config['options']['install_requires'].split('\n'); deps = [x for x in deps if x]; head = '# Autogenerated by Makefile from setup.cfg install_requies section. Remove this line if you want to fix this file.'; path = 'requirements.txt'; f = open(path, 'r') if os.path.exists(path) else None; line = f.readline() if f else ''; quit('User defined requirements already existing.') if f and not line.startswith(head[:20]) else None; f = open('requirements.txt', 'w'); f.write('\n'.join([head] + deps))"
-
-doc: Makefile $(SOURCES) $(DOCUMENTATION) docs/conf.py docs/apidoc-template/*
-	# link apidoc to source and build html documentation with sphinx
-	python setup.py build_sphinx
-	# manual version would be
-	# # link apidoc to source
-	# sphinx-apidoc -o docs/source/ $(MODULE)
-	# # build html documentation with sphinx
-	# # @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(0)
-	# sphinx-build -M html docs docs/_build
-	#
+## Serve the docs (constant update)
+docs-serve: install-docs
+	${CONDA_RUN}sphinx-autobuild docs docs/_build/ -j auto --watch tfields
+	
+## Build the documentation
+docs-build: install-docs
+	# "-M html" specifies that we want to build an HTML version of the documentation.
+	# "docs" specifies the source directory where the documentation files are located.
+	# "docs/_build/" specifies the output directory where the HTML files will be generated.
+	# "-a" tells Sphinx to rebuild all files, even if they haven't been modified.
+	# "-j auto" instructs Sphinx to use multiple processors to speed up the build process.
+	# "-W" turns on warnings during the build process.
+	# "--keep-going" tells Sphinx to continue building even if it encounters errors, rather than stopping the build process immediately.
+	# "-E" flag to show all warnings. Not yet found an option to remove the *args, **kwargs warnings. Should be handled by napoleon when looking to the code. but no Idea
+	${CONDA_RUN}sphinx-build -M html docs docs/_build/ -E -a -j auto --keep-going
+	# No need to call apidoc since autosummary is doing that for you even better.
+
+## Build and open the documentation
+docs: docs-build
 	# open the html slides
-	python -m webbrowser docs/_build/html/index.html
+	${CONDA_RUN}python -m webbrowser docs/_build/html/index.html
 
+## get up to date with the cookiecutter template 'dough'. First check that no changes are existing.
 update:
 	# get up to date with the cookiecutter template 'dough'
 	# first check that no changes are existing
@@ -78,4 +123,47 @@ update:
 	# Starting upgrade
 	cookiecutter_project_upgrader
 
+## build the project for publishing
+build:
+	poetry build
+
+## check the correctness of the build
+check-build: build
+	twine check dist/*
+
+## Bump the version and publish the package via ci to pypi. Args: part - one of major / minor / patch(default) -> 'make part=minor publish'
+publish: lint-flake test check-build requires_version
+	# Makefile laggs afterwards with "poetry version --short" properly so we process the output
+	$(eval NEW_VERSION := $(shell poetry version $(part) --dry-run -s --no-plugins))
+	@echo $(GITSTATUS)
+	@if [ -z $(GITSTATUS) ]; then \
+	  	echo "Working directory clean."; \
+	else \
+		git status; \
+		printf $(_WARN) "WAIT" "Your Git status is not clean!" ; \
+	    if ! $(MAKE) -s confirm ; then \
+		    printf $(_ERROR) "KO" "EXIT" ; \
+	        exit 1; \
+		fi \
+	fi
+	poetry version $(part)
+	git commit -am "release: v$(NEW_VERSION)"
+	git tag "v$(NEW_VERSION)"
+	git push
+	git push --tags
+
+## remove last tag. mostly, because publishing failed
+untag:
+	# remove last tag. mostly, because publishing failed
+	printf $(_WARN) "WAIT" "Do you want to delete the current version tag and push the change?" ; \
+	@if [ $(MAKE) -s confirm ]; then \
+		git tag -d v$(VERSION) ; \
+		git push origin :refs/tags/v$(VERSION) ; \
+	else \
+		printf $(_ERROR) "ko" "exit" ; \
+		exit 1; \
+	fi
+
 FORCE: ;
+
+include tools/Makefile.help
diff --git a/README.rst b/README.rst
index cdccdae486d546387a34a5388623f78c8fb2ce55..0b38c3c43b39d65f6b3397d6e0503df4282fe6f9 100644
--- a/README.rst
+++ b/README.rst
@@ -1,52 +1,69 @@
-=========================
-Introduction to `tfields`
-=========================
+library documentation
+=====================
 
+..
+    [shields-start]
 
 .. pypi
 .. image:: https://img.shields.io/pypi/v/tfields.svg
     :target: https://pypi.python.org/pypi/tfields
-
 .. ci
-    .. image:: https://img.shields.io/travis/dboe/tfields.svg
-        :target: https://travis-ci.com/dboe/tfields
 .. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/badges/master/pipeline.svg
-    :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/commits/master
+    :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/pipelines/latest
+
+.. latest release
+.. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/badges/release.svg
+    :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/-/releases
 
 .. coverage
 .. image:: https://gitlab.mpcdf.mpg.de/dboe/tfields/badges/master/coverage.svg
     :target: https://gitlab.mpcdf.mpg.de/dboe/tfields/commits/master
 
-.. readthedocs
-.. image:: https://readthedocs.org/projects/tfields/badge/?version=latest
-    :target: https://tfields.readthedocs.io/en/latest/?badge=latest
-    :alt: Documentation Status
-
-.. pyup crosschecks your dependencies. Github is default, gitlab more complicated: https://pyup.readthedocs.io/en/latest/readme.html#run-your-first-update 
-    .. image:: https://pyup.io/repos/github/dboe/tfields/shield.svg
-        :target: https://pyup.io/repos/github/dboe/tfields/
-        :alt: Updates
+.. docs
+.. image:: https://img.shields.io/website-up-down-green-red/https/dboe.pages.mpcdf.de/tfields.svg?label=docs
+    :target: https://dboe.pages.mpcdf.de/tfields
 
+.. pre-commit
 .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
    :target: https://github.com/pre-commit/pre-commit
    :alt: pre-commit
 
+..
+    [shields-end]
+
+Overview
+--------
+
+..
+    [overview-start]
 
 Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation.
 
+..
+    [overview-end]
 
 Licensed under the ``MIT License``
 
 Resources
----------
+~~~~~~~~~
+
+..
+    [resources-start]
 
 * Source code: https://gitlab.mpcdf.mpg.de/dboe/tfields
-* Documentation: https://tfields.readthedocs.io
+* Documentation: https://dboe.pages.mpcdf.de/tfields
 * Pypi: https://pypi.python.org/pypi/tfields
 
 
+..
+    [resources-end]
+
+
 Features
---------
+~~~~~~~~
+
+..
+    [features-start]
 
 The following features should be highlighted:
 
@@ -55,6 +72,9 @@ The following features should be highlighted:
 * TensorMaps with fields
 * Mesh manipulation by graph theory
 * TODO
+  
+..
+    [features-end]
 
 Review of alternative/supplementary APIs
 ----------------------------------------
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf91f342d2ca94fc00d952513d6f08b723f3e55c
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,52 @@
+import pytest
+import unittest.mock
+import logging
+
+
+# To add auto-imports for doctests
+# import numpy
+# @pytest.fixture(autouse=True)
+# def add_np(doctest_namespace):
+#     doctest_namespace["np"] = numpy
+
+
+# Patching plotting
+@pytest.fixture(scope="session", autouse=True)
+def default_session_fixture(request):
+    """
+    :type request: _pytest.python.SubRequest
+    :return:
+    """
+    log = logging.getLogger()
+    log.info("Patching `show`")
+    patch = unittest.mock.patch("matplotlib.pyplot.show")
+    if not request.config.getoption("--plot"):
+        patch.start()
+
+
+def pytest_addoption(parser):
+    parser.addoption(
+        "--plot", action="store_true", default=False, help="Activate plot tests"
+    )
+    parser.addoption(
+        "--slow", action="store_true", default=False, help="Activate slow tests"
+    )
+
+
+def pytest_configure(config):
+    config.addinivalue_line(
+        "markers", "plot: mark test as requiring visual assertion by human"
+    )
+    config.addinivalue_line("markers", "slow: mark test as slow")
+
+
+def pytest_collection_modifyitems(config, items):
+    if config.getoption("--plot") or config.getoption("--slow"):
+        return
+    skip_plot = pytest.mark.skip(reason="need --plot option to run")
+    skip_slow = pytest.mark.skip(reason="need --slow option to run")
+    for item in items:
+        if "plot" in item.keywords:
+            item.add_marker(skip_plot)
+        if "slow" in item.keywords:
+            item.add_marker(skip_slow)
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 8607fce7795fab193404dac8f276a887a9c90a15..0000000000000000000000000000000000000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,25 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-MODULE_PATH := $(shell cd ..; pwd)
-MODULE := $(shell basename "$(MODULE_PATH)")
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = .
-BUILDDIR      = _build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-apidocs: $(MODULEPATH)/$(MODULE)/*.py apidoc-templates/*
-	 sphinx-apidoc -o source/ ../$(MODULE)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/index.css b/docs/_static/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..40f044c1fe244332b88af989894dd41a0c5a96b8
--- /dev/null
+++ b/docs/_static/index.css
@@ -0,0 +1,73 @@
+@font-face {
+	font-family: 'inter';
+	src: url('inter.ttf') format('truetype');
+}
+
+#landing-header {
+	width: 100%;
+	display: flex;
+	flex-wrap: wrap;
+	margin-left: auto;
+	margin-top: 1rem;
+	font-family: Inter;
+}
+
+#landing-header > img {
+	width: 75%;
+	margin: auto;
+}
+
+#landing-tagline {
+	width: 100%;
+	text-align: center;
+	font-size: xx-large;
+}
+
+
+#landing-overview {
+	width: 90%;
+	display: flex;
+	flex-wrap: wrap;
+	gap: 1rem;
+	margin-top: 4rem;
+	margin-left: auto;
+	margin-right: auto;
+	justify-content: space-around;
+}
+
+
+
+@media (min-width: 992px) {
+	#landing-overview > section {
+		width: 45% !important;
+	}
+}
+
+@media (min-width: 1400px) {
+	#landing-overview > section {
+		width: 30% !important;
+	}
+}
+
+#landing-overview > section {
+	font-size: medium;
+	background-color: var(--pst-color-on-background);
+	padding: .75rem;
+	border-radius: 5px;
+	text-align: justify;
+}
+
+
+
+#landing-overview > section  h2 {
+	margin-top: 0;
+	font-weight: bold;
+	font-size: medium;
+	text-transform: uppercase;
+	color: #edb641;
+}
+
+
+#landing-overview > section > p {
+	width: 100%;
+}
\ No newline at end of file
diff --git a/docs/_static/inter.ttf b/docs/_static/inter.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ec3164efa8fe938310f74b6156d3ad33ccb2fef6
Binary files /dev/null and b/docs/_static/inter.ttf differ
diff --git a/docs/_static/style.css b/docs/_static/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..ca785ff3c5af458022c6a48edd3d319e3dfe51fb
--- /dev/null
+++ b/docs/_static/style.css
@@ -0,0 +1,93 @@
+.article-container p {
+	text-align: justify;
+}
+
+#version-warning {
+	top: 0;
+	position: sticky;
+	z-index: 60;
+	width: 100%;
+	height: 2.5rem;
+	display: flex;
+	column-gap: .5rem;
+	justify-content: center;
+	justify-items: center;
+	align-items: center;
+	background-color: #eee;
+	border-bottom: 2px solid #ae2828;
+}
+
+@media (prefers-color-scheme: dark) {
+	body:not([data-theme="light"]) #version-warning {
+		background-color: black;
+	}
+}
+
+
+table.docutils {
+	clear: left;
+	float: left;
+	margin: 0 1rem 1rem;
+}
+
+
+/* theme customization */
+
+html[data-theme="dark"] {
+	--color-background-primary: #1d2433;
+	--color-background-secondary: #0c101a;
+	--pst-color-on-background: #2e374a;
+	--pst-color-border: #2f2f2f;
+}
+
+html[data-theme="light"] {
+	--color-background-primary: #f8f9fb;
+	--color-background-secondary: #fff;
+}
+
+html[data-theme="light"], html[data-theme="dark"] {
+	--pst-color-background: var(--color-background-primary);
+	--pst-color-primary: #ffae57;
+}
+
+
+@media (min-width: 960px) {
+	.bd-page-width {
+		max-width: 100rem;
+	}
+}
+
+.bd-sidebar-primary {
+	display: block;
+	flex: 0 0 18%;
+}
+
+
+.bd-content, .bd-sidebar-secondary {
+	background-color: var(--color-background-secondary);
+}
+
+
+.sd-tab-content {
+	box-shadow: 0 -0.0625rem var(--sd-color-tabs-overline);
+}
+
+div.literal-block-wrapper, pre {
+	border: none;
+}
+
+.code-block-caption {
+	background-color: var(--pst-color-surface);
+}
+
+.navbar-persistent--mobile {
+	margin-left: unset;
+}
+
+.navbar-persistent--mobile:last-of-type {
+	padding-left: 1rem;
+}
+
+#navbar-icon-links a {
+	padding-top: .2rem;
+}
\ No newline at end of file
diff --git a/docs/_static/versioning.js b/docs/_static/versioning.js
new file mode 100644
index 0000000000000000000000000000000000000000..15f6c9b301978bd4e8583edc2d7696903bd0340c
--- /dev/null
+++ b/docs/_static/versioning.js
@@ -0,0 +1,114 @@
+const loadVersions = async () => {
+    const res = await fetch(DOCUMENTATION_OPTIONS.URL_ROOT + "versions.json")
+    if (res.status !== 200) {
+        return null
+    }
+    return await res.json()
+}
+
+
+const getCurrentVersion = (versions) => {
+    const baseURL = new URL(DOCUMENTATION_OPTIONS.URL_ROOT, window.location).href
+    const parts = window.location.href.replace(baseURL, "").split("/")
+    if (!parts.length) {
+        return null
+    }
+    const maybeVersion = parts[0]
+    if (maybeVersion === "lib") {
+        return versions.latest
+    }
+    if (versions.versions.includes(maybeVersion)) {
+        return maybeVersion
+    }
+    return null
+}
+
+
+const addVersionWarning = (currentVersion, latestVersion) => {
+    if (currentVersion === latestVersion) {
+        return
+    }
+
+    const navbarMain = document.getElementById("navbar-main")
+    if (!navbarMain) {
+        return
+    }
+
+    const container = document.createElement("div")
+    container.id = "version-warning"
+
+    const warningText = document.createElement("span")
+    warningText.textContent = `You are viewing the documentation for ${currentVersion === "dev" ? "a preview" : "an outdated"} version of this project.`
+    container.appendChild(warningText)
+
+    const latestLink = document.createElement("a")
+    latestLink.textContent = "Click here to go to the latest version"
+    latestLink.href = DOCUMENTATION_OPTIONS.URL_ROOT + "lib"
+    container.appendChild(latestLink)
+
+    navbarMain.before(container)
+}
+
+
+const formatVersionName = (version, isLatest) => version + (isLatest ? " (latest)" : "")
+
+
+const addVersionSelect = (currentVersion, versionSpec) => {
+    const navEnd = document.getElementById("navbar-end")
+    if (!navEnd) {
+        return
+    }
+
+    const container = document.createElement("div")
+    container.classList.add("navbar-nav")
+
+    const dropdown = document.createElement("div")
+    dropdown.classList.add("dropdown")
+    container.appendChild(dropdown)
+
+    const dropdownToggle = document.createElement("button")
+    dropdownToggle.classList.add("btn", "dropdown-toggle", "nav-item")
+    dropdownToggle.setAttribute("data-toggle", "dropdown")
+    dropdownToggle.setAttribute("type", "button")
+    dropdownToggle.textContent = `Version: ${formatVersionName(currentVersion, currentVersion === versionSpec.latest)}`
+    dropdown.appendChild(dropdownToggle)
+
+    const dropdownContent = document.createElement("div")
+    dropdownContent.classList.add("dropdown-menu")
+    dropdown.appendChild(dropdownContent)
+
+    for (const version of versionSpec.versions) {
+        const navItem = document.createElement("li")
+        navItem.classList.add("nav-item")
+
+        const navLink = document.createElement("a")
+        navLink.classList.add("nav-link", "nav-internal")
+        navLink.href = DOCUMENTATION_OPTIONS.URL_ROOT + version
+        navLink.textContent = formatVersionName(version, version === versionSpec.latest)
+        navItem.appendChild(navLink)
+
+        dropdownContent.appendChild(navItem)
+    }
+
+    navEnd.prepend(container)
+}
+
+
+const setupVersioning = (versions) => {
+    if (versions === null) {
+        return
+    }
+
+    const currentVersion = getCurrentVersion(versions)
+    if (currentVersion === null) {
+        return
+    }
+
+    addVersionWarning(currentVersion, versions.latest)
+    addVersionSelect(currentVersion, versions)
+}
+
+
+window.addEventListener("DOMContentLoaded", () => {
+    loadVersions().then(setupVersioning)
+})
diff --git a/docs/_templates/custom-class-template.rst b/docs/_templates/custom-class-template.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7c95a18710ac67543f7ebabaa0be1ba7d3ff9c4e
--- /dev/null
+++ b/docs/_templates/custom-class-template.rst
@@ -0,0 +1,34 @@
+{{ fullname | escape | underline}}
+
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ objname }}
+   :members:
+   :show-inheritance:
+   :inherited-members:
+   :special-members: __call__, __add__, __mul__, __iter__, __item__
+
+   {% block methods %}
+   {% if methods %}
+   .. rubric:: {{ _('Methods') }}
+
+   .. autosummary::
+      :nosignatures:
+   {% for item in methods %}
+      {%- if not item.startswith('_') %}
+      ~{{ name }}.{{ item }}
+      {%- endif -%}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block attributes %}
+   {% if attributes %}
+   .. rubric:: {{ _('Attributes') }}
+
+   .. autosummary::
+   {% for item in attributes %}
+      ~{{ name }}.{{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
diff --git a/docs/_templates/custom-module-template.rst b/docs/_templates/custom-module-template.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d066d0e4dc2e34f399f55ea38d92ed4540787317
--- /dev/null
+++ b/docs/_templates/custom-module-template.rst
@@ -0,0 +1,66 @@
+{{ fullname | escape | underline}}
+
+.. automodule:: {{ fullname }}
+
+   {% block attributes %}
+   {% if attributes %}
+   .. rubric:: Module attributes
+
+   .. autosummary::
+      :toctree:
+   {% for item in attributes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block functions %}
+   {% if functions %}
+   .. rubric:: {{ _('Functions') }}
+
+   .. autosummary::
+      :toctree:
+      :nosignatures:
+   {% for item in functions %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block classes %}
+   {% if classes %}
+   .. rubric:: {{ _('Classes') }}
+
+   .. autosummary::
+      :toctree:
+      :template: custom-class-template.rst
+      :nosignatures:
+   {% for item in classes %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+   {% block exceptions %}
+   {% if exceptions %}
+   .. rubric:: {{ _('Exceptions') }}
+
+   .. autosummary::
+      :toctree:
+   {% for item in exceptions %}
+      {{ item }}
+   {%- endfor %}
+   {% endif %}
+   {% endblock %}
+
+{% block modules %}
+{% if modules %}
+.. autosummary::
+   :toctree:
+   :template: custom-module-template.rst
+   :recursive:
+{% for item in modules %}
+   {{ item }}
+{%- endfor %}
+{% endif %}
+{% endblock %}
diff --git a/docs/_templates/navbar-nav.html b/docs/_templates/navbar-nav.html
new file mode 100644
index 0000000000000000000000000000000000000000..72ed846db9401cf32d433408b92160e437407451
--- /dev/null
+++ b/docs/_templates/navbar-nav.html
@@ -0,0 +1,32 @@
+{% macro nav_link(label, target) -%}
+    <li class="nav-item">
+        <a class="nav-link nav-internal" href="{{ target if target.startswith('http') else pathto(target) }}">{{ label }}</a>
+    </li>
+{%- endmacro %}
+
+
+<nav class="navbar-nav">
+    <p class="sidebar-header-items__title"
+       role="heading"
+       aria-level="1"
+       aria-label="{{ _('Site Navigation') }}">
+        {{ _("Site Navigation") }}
+    </p>
+    <ul id="navbar-main-elements" class="navbar-nav">
+        {% for label, item in navbar_items | items %}
+            {% if item is string %}
+                {{ nav_link(label, item) }}
+            {% else %}
+                <div class="nav-item dropdown">
+                    <button class="btn dropdown-toggle nav-item" type="button"
+                            data-toggle="dropdown">{{ label }}</button>
+                    <ul class="dropdown-menu">
+                        {% for child_label, child_item in item | items %}
+                            {{ nav_link(child_label, child_item) }}
+                        {% endfor %}
+                    </ul>
+                </div>
+            {% endif %}
+        {% endfor %}
+    </ul>
+</nav>
diff --git a/docs/about/index.rst b/docs/about/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..befeb16595a8379cde5ae67cac41e4f63c24ca18
--- /dev/null
+++ b/docs/about/index.rst
@@ -0,0 +1,5 @@
+=====
+ABOUT
+=====
+
+.. include:: ../../AUTHORS.rst
diff --git a/docs/apidoc-template/toc.rst_t b/docs/apidoc-template/toc.rst_t
deleted file mode 100644
index 8d0b85202138fc70888abde9b0aec3ce0679c2af..0000000000000000000000000000000000000000
--- a/docs/apidoc-template/toc.rst_t
+++ /dev/null
@@ -1,10 +0,0 @@
-
-=================
-API Documentation
-=================
-
-.. toctree::
-   :maxdepth: {{ maxdepth }}
-{% for docname in docnames %}
-   {{ docname }}
-{%- endfor %}
diff --git a/docs/authors.rst b/docs/authors.rst
deleted file mode 100644
index e122f914a87b277e565fc9567af1a7545ec9872b..0000000000000000000000000000000000000000
--- a/docs/authors.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../AUTHORS.rst
diff --git a/docs/community/index.rst b/docs/community/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ac7b6bcf35e98139c791cb5c4b94f08849972d65
--- /dev/null
+++ b/docs/community/index.rst
@@ -0,0 +1 @@
+.. include:: ../../CONTRIBUTING.rst
diff --git a/docs/conf.py b/docs/conf.py
index 9f12f9389ddbe0fbc20c0671f5dca4b1500194df..f51b461e9f8f1366a2ccc7ff64987ac326cf2561 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,195 +1,108 @@
-#!/usr/bin/env python
-"""
-This file is execfile()d with the current directory set to its
-containing dir.
-
-Note that not all possible configuration values are present in this
-autogenerated file.
-
-All configuration values have a default; values that are commented out
-serve to show the default.
-
-If extensions (or modules to document with autodoc) are in another
-directory, add these directories to sys.path here. If the directory is
-relative to the documentation root, use os.path.abspath to make it
-absolute, like shown here.
-"""
 import os
-import subprocess
-import configparser
 import datetime
+try:
+    import tomllib
+except ModuleNotFoundError:
+    import tomli as tomllib
 
-config = configparser.ConfigParser()
-script_dir = os.path.dirname(__file__)
-path = os.path.join(script_dir, "../setup.cfg")
-config.read(path)
-__version__ = config["bumpversion"]["current_version"]
-name = config["metadata"]["name"]
-source_dir = config["build_sphinx"]["source-dir"]
+__source_dir = "docs"
+with open(os.path.join(os.path.dirname(__file__), "../pyproject.toml"), "rb") as f:
+    __config = tomllib.load(f)
 
-# -- General configuration ---------------------------------------------
+project = __config["tool"]["poetry"]["name"]
+author = __config["tool"]["poetry"]["authors"][0]
+copyright = str(datetime.date.today().year) + ", " + author
+release = __config["tool"]["poetry"]["version"]
 
-# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = "2.2.0"
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = [
-    "sphinx.ext.autosectionlabel",  # links
-    "sphinx.ext.napoleon",
-    "sphinx.ext.todo",
-    "sphinx.ext.doctest",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.autosectionlabel",
     "sphinx.ext.autodoc",
-    "sphinx.ext.viewcode",
+    "sphinx.ext.napoleon",
+    "sphinx_design",
+    "sphinx_copybutton",
+    "sphinxcontrib.mermaid",
+    "sphinx.ext.viewcode",  # Add links to highlighted source code
+    "sphinx.ext.doctest",  # Test snippets in the documentation
+    "sphinx.ext.autosummary",
+    "sphinx_autodoc_typehints",  # Automatically parse type hints
 ]
+autosummary_generate = True  # Turn on sphinx.ext.autosummary
 
-
-def setup(app):
-    """
-    Automatically run apidoc
-    see https://github.com/sphinx-doc/sphinx/issues/1861
-    """
-    # distinguish when calling from project root dir and from docs dir
-    if os.path.basename(os.path.abspath(".")) == source_dir:
-        subprocess.call(
-            [
-                "sphinx-apidoc",
-                "-o",
-                "source/",
-                "../" + name,
-                "--templatedir",
-                "apidoc-template",
-            ]
-        )
-    else:
-        subprocess.call(
-            [
-                "sphinx-apidoc",
-                "-o",
-                source_dir + "/source/",
-                name,
-                "--templatedir",
-                source_dir + "/apidoc-template",
-            ]
-        )
-
-
-# Add any paths that contain templates here, relative to this directory.
 templates_path = ["_templates"]
+exclude_patterns = ["_build"]
 
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-source_suffix = ".rst"
-
-# The master toctree document.
-master_doc = "index"
-
-# General information about the project.
-project = name
-year = datetime.date.today().year
-author = config["metadata"]["author"]
-copyright = str(year) + ", " + author
-
-# The version info for the project you're documenting, acts as replacement
-# for |version| and |release|, also used in various other places throughout
-# the built documents.
-#
-# The short X.Y version.
-version = __version__
-# The full version, including alpha/beta/rc tags.
-release = __version__
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = "sphinx"
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-
-# -- Options for HTML output -------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#
-html_theme = "classic"
-
-# Theme options are theme-specific and customize the look and feel of a
-# theme further.  For a list of options available for each theme, see the
-# documentation.
-#
-# html_theme_options = {}
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-
-# -- Options for HTMLHelp output ---------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = name + "doc"
-
-
-# -- Options for LaTeX output ------------------------------------------
-
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    #
-    # 'papersize': 'letterpaper',
-    # The font size ('10pt', '11pt' or '12pt').
-    #
-    # 'pointsize': '10pt',
-    # Additional stuff for the LaTeX preamble.
-    #
-    # 'preamble': '',
-    # Latex figure (float) alignment
-    #
-    # 'figure_align': 'htbp',
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3", None),
 }
 
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass
-# [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, name + ".tex", name + " Documentation", author, "manual"),
-]
-
-
-# -- Options for manual page output ------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [(master_doc, name, name + " Documentation", [author], 1)]
-
-
-# -- Options for Texinfo output ----------------------------------------
+napoleon_google_docstring = True
+napoleon_include_special_with_doc = True
+napoleon_use_admonition_for_examples = True
+napoleon_use_admonition_for_notes = True
+napoleon_use_admonition_for_references = False
+napoleon_attr_annotations = True
+
+html_show_sourcelink = (
+    False  # Remove 'view source code' from top of page (for html, not python)
+)
+autodoc_inherit_docstrings = True  # If no docstring, inherit from base class
+set_type_checking_flag = True  # Enable 'expensive' imports for sphinx_autodoc_typehints
+nbsphinx_allow_errors = True  # Continue through Jupyter errors
+# autodoc_typehints = "description" # Sphinx-native method. Not as good as sphinx_autodoc_typehints
+add_module_names = False  # Remove namespaces from class/method signatures
+autoclass_content = (
+    "both"  # Both the class’ and the __init__ method’s docstring are concatenated
+)
+autodoc_class_signature = "separated"
+autodoc_default_options = {
+    "show-inheritance": True,
+}
+autodoc_member_order = "bysource"
+autodoc_typehints_format = "short"
+
+always_document_param_types = True
+typehints_defaults = "comma"
+
+auto_pytabs_min_version = (3, 7)
+auto_pytabs_max_version = (3, 11)
+
+autosectionlabel_prefix_document = True
+
+suppress_warnings = ["autosectionlabel.*"]
+
+html_theme = "pydata_sphinx_theme"
+html_static_path = ["_static"]
+html_css_files = ["style.css"]
+html_js_files = ["versioning.js"]
+html_favicon = "images/favicon.ico"
+html_logo = "images/logo.svg"
+html_show_sourcelink = False
+html_sidebars = {"about/*": []}
+html_title = f"{project} Framework"
+
+html_theme_options = {
+    "use_edit_page_button": False,
+    "show_toc_level": 4,
+    "navbar_align": "left",
+    "icon_links": [
+        {
+            "name": "Source",
+            "url": __config["tool"]["poetry"]["repository"],
+            "icon": f"fa-brands fa-{'github' if 'github' in __config['tool']['poetry']['repository'] else 'gitlab'}",
+            "type": "fontawesome",
+        },
+    ],
+    "navbar_end": ["navbar-icon-links"],
+    "navbar_persistent": ["search-button", "theme-switcher"],
+}
 
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (
-        master_doc,
-        name,
-        name + " Documentation",
-        author,
-        name,
-        "One line description of project.",
-        "Miscellaneous",
-    ),
-]
+html_context = {
+    "navbar_items": {
+        "Documentation": "lib/index",
+        "Community": {
+            "Contribution guide": "community/index",
+        },
+        "About": "about/index",
+    }
+}
diff --git a/docs/contributing.rst b/docs/contributing.rst
deleted file mode 100644
index e582053ea018c369be05aae96cf730744f1dc616..0000000000000000000000000000000000000000
--- a/docs/contributing.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../CONTRIBUTING.rst
diff --git a/docs/cookiecutter_input.json b/docs/cookiecutter_input.json
index 539ee6e426ba12a983e41954677ec43c156596ef..c28c84939f530808036d925f4be6923267446a21 100644
--- a/docs/cookiecutter_input.json
+++ b/docs/cookiecutter_input.json
@@ -1,5 +1,4 @@
 {
-    "_output_dir": "/mnt/data/git/tfields/.git/cookiecutter",
     "_template": "https://gitlab.com/dboe/dough.git",
     "author": "Daniel B\u00f6ckenhoff",
     "continuous_integration": "y",
diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..e0482a1bebb6c86187c50d00045e5ed9e1316b58
Binary files /dev/null and b/docs/images/favicon.ico differ
diff --git a/docs/images/logo.svg b/docs/images/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8e8ae8a9db12740245deaac106eadb7037d78179
--- /dev/null
+++ b/docs/images/logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 21v2.5l-3-2-3 2V21h-.5A3.5 3.5 0 0 1 3 17.5V5a3 3 0 0 1 3-3h14a1 1 0 0 1 1 1v17a1 1 0 0 1-1 1h-7zm-6-2v-2h6v2h6v-3H6.5a1.5 1.5 0 0 0 0 3H7zM7 5v2h2V5H7zm0 3v2h2V8H7zm0 3v2h2v-2H7z"/></svg>
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 28abcdcb0638450427862045129470ecf835065a..26edbe1df3120d15a961fa41d269884b8044ecba 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,19 +1,74 @@
-Welcome to tfields's documentation!
-======================================
+.. div:: sd-text-center sd-text-primary sd-fs-1 sd-font-weight-bolder -sd-text-nowra
+   
+   tfields
+
+.. card::
+   :text-align: center
+   :shadow: none
+   :class-card: sd-border-0
+
+   .. include:: ../README.rst
+      :start-after: [shields-start]
+      :end-before: [shields-end]
+      
+.. grid:: 1 1 2 2
+    :gutter: 1
+
+    .. grid-item::
+
+        .. grid:: 1 1 1 1
+            :gutter: 1
+
+            .. grid-item-card:: Overview
+               :link: lib/overview/index
+               :link-type: doc
+
+               .. include:: ../README.rst
+                  :start-after: [overview-start]
+                  :end-before: [overview-end]
+
+            .. grid-item-card:: Installation
+               :link: lib/installation/index
+               :link-type: doc
+
+               .. include:: lib/installation/index.rst
+                  :start-after: [essence-start]
+                  :end-before: [essence-end]
+
+    .. grid-item::
+
+        .. grid:: 1 1 1 1
+            :gutter: 1
+
+            .. grid-item-card:: Usage
+               :link: lib/usage/index
+               :link-type: doc
+
+               .. include:: lib/usage/index.rst
+                  :start-after: [essence-start]
+                  :end-before: [essence-end]
+
+            .. grid-item-card:: API reference
+               :link: lib/reference/_autosummary/tfields
+               :link-type: doc
+               :text-align: center
+
+               :octicon:`codescan;5em;sd-text-info`
+
+            .. grid-item-card:: API development
+               :link: community/index
+               :link-type: doc
+               
+               
+               .. code-block:: shell
+                  
+                  make install
+                  make test
+                  make publish
 
 .. toctree::
-   :maxdepth: 2
-   :caption: Contents:
-
-   readme
-   installation
-   usage
-   source/modules
-   contributing
-   authors
-
-Indices and tables
-==================
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+   :hidden:
+
+   about/index
+   community/index
+   lib/index
diff --git a/docs/installation.rst b/docs/installation.rst
deleted file mode 100644
index 4fbc5858ba725b89e350ae1e88d6b17149f827cd..0000000000000000000000000000000000000000
--- a/docs/installation.rst
+++ /dev/null
@@ -1,52 +0,0 @@
-.. highlight:: shell
-
-============
-Installation
-============
-
-
-Stable release
---------------
-
-To install tfields, run this command in your terminal:
-
-.. code-block:: console
-
-    $ pip install tfields
-
-This is the preferred method to install tfields, as it will always install the most recent stable release.
-
-If you don't have `pip`_ installed, this `Python installation guide`_ can guide
-you through the process.
-
-.. _pip: https://pip.pypa.io
-.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
-
-
-
-From sources
-------------
-
-The sources for tfields can be downloaded from the `remote repo`_.
-
-You can either clone the public repository:
-
-.. code-block:: console
-
-    $ git clone git://gitlab.mpcdf.mpg.de/dboe/tfields
-
-Or download the `tarball`_:
-
-.. code-block:: console
-
-    $ curl -OJL https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master
-
-Once you have a copy of the source, you can install it with:
-
-.. code-block:: console
-
-    $ python setup.py install
-
-
-.. _remote repo: https://gitlab.mpcdf.mpg.de/dboe/tfields
-.. _tarball: https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master
diff --git a/docs/lib/index.rst b/docs/lib/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..50a175d20d6b6074f75ef43944023ab5337da38a
--- /dev/null
+++ b/docs/lib/index.rst
@@ -0,0 +1,14 @@
+Library documentation
+=====================
+
+.. include:: overview/index.rst
+
+.. toctree::
+    :caption: Documentation
+    :hidden:
+    :maxdepth: 2
+
+    overview/index
+    installation/index
+    usage/index
+    reference/index
\ No newline at end of file
diff --git a/docs/lib/installation/index.rst b/docs/lib/installation/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..759a8a76fd458f8b2a70553f33e7263375ca76dd
--- /dev/null
+++ b/docs/lib/installation/index.rst
@@ -0,0 +1,70 @@
+============
+Installation
+============
+..
+    How to get it and set it up?
+    [installation]
+
+To install tfields, you have the following options:
+
+.. tab-set::
+
+    .. tab-item:: PyPi :octicon:`package`
+
+        The preferred method to install tfields is to get the most recent stable release from PyPi:
+        
+        ..
+            [essence-start]
+
+        .. code-block:: shell
+       
+           pip install tfields
+           
+        ..
+            [essence-end]
+
+        If you don't have `pip`_ installed, this `Python installation guide`_ can guide you through the process.
+        
+        .. _pip: https://pip.pypa.io
+        .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
+
+        .. dropdown:: Extras
+            :icon: star
+            
+            Install a special extra:
+                :code:`pip install tfields[extra]`
+
+            All extras:
+                :code:`pip install tfields[full]`
+
+    .. tab-item:: Source :octicon:`code`
+
+        First you have to retrieve the source code of tfields.
+        You have the following options:
+        
+        .. tab-set::
+        
+            .. tab-item:: Git :octicon:`git-branch`
+
+                To clone the public repository run
+
+                .. code-block:: shell
+
+                    git clone git://gitlab.mpcdf.mpg.de/dboe/tfields
+
+            .. tab-item:: Tarball :octicon:`gift`
+
+                Either download the tarball `here <https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master>`_ or run
+
+                .. code-block:: shell
+
+                    curl -OJL https://gitlab.mpcdf.mpg.de/dboe/tfields/tarball/master
+
+                
+        Once you have a copy of the source, navigate inside and install it with:
+
+        .. code-block:: shell
+
+            poetry install
+
+
diff --git a/docs/lib/overview/index.rst b/docs/lib/overview/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bc3124fff4a97d04a5e0bf65120c8cd633a11adf
--- /dev/null
+++ b/docs/lib/overview/index.rst
@@ -0,0 +1,28 @@
+Overview
+========
+
+..
+   What is it? Why should I use it?
+   [overview-start]
+
+.. include:: ../../../README.rst
+   :start-after: [overview-start]
+   :end-before: [overview-end]
+
+..
+   [overview-end]
+
+Features
+--------
+
+..
+   What can it do?
+   [features]
+
+.. include:: ../../../README.rst
+   :start-after: [features-start]
+   :end-before: [features-end]
+
+..
+   [features-end]
+
diff --git a/docs/lib/reference/index.rst b/docs/lib/reference/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..799c111a3280523ca7624a72a7e6fc918378f2c2
--- /dev/null
+++ b/docs/lib/reference/index.rst
@@ -0,0 +1,14 @@
+=============
+API reference
+=============
+
+..
+    How to dive deeper? Give me the api?
+    [reference]
+    
+.. autosummary::
+    :toctree: _autosummary
+    :template: custom-module-template.rst
+    :recursive:
+
+    tfields
diff --git a/docs/lib/usage/index.rst b/docs/lib/usage/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b7fd3e9ec356874fe953d60c76f6802e0362c1ac
--- /dev/null
+++ b/docs/lib/usage/index.rst
@@ -0,0 +1,18 @@
+=====
+Usage
+=====
+..
+    How to use? Give me a primer.
+    [usage]
+    
+..
+    [essence-start]
+
+To use tfields in a project use
+
+.. code-block:: python
+   
+   import tfields
+    
+..
+    [essence-end]
diff --git a/docs/readme.rst b/docs/readme.rst
deleted file mode 100644
index 72a33558153fb57def85612b021ec596ef2a51b9..0000000000000000000000000000000000000000
--- a/docs/readme.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../README.rst
diff --git a/docs/usage.rst b/docs/usage.rst
deleted file mode 100644
index c0d9b7748c4e7115601cd41a0ef5e6646ed8f836..0000000000000000000000000000000000000000
--- a/docs/usage.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-=====
-Usage
-=====
-
-To use tfields in a project::
-
-    >>> import tfields
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7ea3a24c98a2226fce098bd9e62d6e216a15d6c
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,18 @@
+name: tfields
+channels:
+  - conda-forge
+  # We want to have a reproducible setup, so we don't want default channels,
+  # which may be different for different users. All required channels should
+  # be listed explicitly here.
+  - nodefaults
+dependencies:
+  - python=3.10.*  # or don't specify the version and use the latest stable Python
+
+  - mamba
+  - conda-lock
+  - "poetry>=1.4"
+  - pre_commit
+  - "pip>=21.2"  # pip must be mentioned explicitly, or conda-lock will fail
+  - "numpy>=1.20.0"  # https://stackoverflow.com/questions/66060487/valueerror-numpy-ndarray-size-changed-may-indicate-binary-incompatibility-exp
+  - pip:
+    - tqdm
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..3d501370bf93db0856fd45d16f88ce829a3bff3f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,129 @@
+[tool.poetry]
+name = "tfields"
+version = "0.4.0"
+description = "Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation."
+authors = ["Daniel Böckenhoff <dboe@ipp.mpg.de>"]
+license = "MIT License"
+readme = "README.rst"
+repository = "https://gitlab.mpcdf.mpg.de/dboe/tfields"
+documentation = "https://dboe.pages.mpcdf.de/tfields"
+classifiers = [
+    # find the full list of possible classifiers at https://pypi.org/classifiers/
+    "Development Status :: 3 - Alpha",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+]
+keywords = ["tensors", " tensor-fields", " graphs", " mesh", " numpy", " math"]
+
+[tool.poetry.dependencies]
+python = "^3.7"
+pathlib = { version = "*", python = "<3.10" }
+numpy = ">=1.20.0"
+sympy = "<=1.6.2"
+scipy = "*"
+rna = ">=0.6.3"
+sortedcontainers = "*"
+numpy-stl = { version = "*", optional = true}
+
+[tool.poetry.group.dev.dependencies]
+poetry-bumpversion = "*"
+ensureconda = "*"  # is part of conda-lock either way
+conda-lock = "*"
+tomli = { version = ">=1.1.0", python = "<3.11" }
+pytest = "*"
+pytest-cov = "*"
+pytest-shutil = "*"
+pytest-virtualenv = "*"
+pytest-fixture-config = "*"
+pytest-xdist = "*"
+coverage = { version = "*", extras = ["toml"]}
+pylint = "*"
+flake8 = "*"
+ipdb = "*"
+mypy = "*"
+twine  = "*"  # for publishing
+pre-commit = "*"  # https://pre-commit.com/ for hook managment
+pre-commit-hooks = "*"
+sphinx = "*"  # for documentation
+cookiecutter_project_upgrader = "*"
+
+[tool.poetry.group.docs]
+optional = true
+
+[tool.poetry.group.docs.dependencies]
+black = ">=22.12.0"
+httpx = ">=0.23.2"
+uvicorn = ">=0.20.0"
+sphinx-autobuild = ">=2021.3.14"
+"sphinx-autopackagesummary" = ">=1.3"
+sphinx-design = ">=0.3.0"
+sphinx = ">=5.3.0"
+sphinx-toolbox = ">=3.2.0"
+sphinx-copybutton = ">=0.5.1"
+sphinxcontrib-mermaid = ">=0.7.1"
+pydata-sphinx-theme = ">=0.12.0"
+sphinx-autodoc-typehints = ">=1.22"
+
+[tool.poetry.extras]
+io = ["numpy-stl"]
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.coverage.report]
+show_missing = true
+exclude_lines = [
+    "pragma: no cover",
+    "if False",
+]
+[tool.coverage.run]
+omit = [
+	"tfields/plotting/*.py"
+]
+
+[tool.poetry_bumpversion.file."tfields/__init__.py"]
+search = '__version__ = "{current_version}"'
+replace = '__version__ = "{new_version}"'
+
+[tool.poetry_bumpversion.file."docs/cookiecutter_input.json"]
+search = '"package_version": "{current_version}"'
+replace = '"package_version": "{new_version}"'
+
+[tool.pytest.ini_options]
+addopts = """
+--doctest-modules
+--ignore-glob=*/bin/activate
+--ignore=performance
+"""
+junit_family = "xunit2"
+
+[tool.tox]
+legacy_tox_ini = """
+    [tox]
+    isolated_build = true
+    skip_missing_interpreters = true
+    recreate = true
+    min_version = 4.0
+    env_list =
+        py35
+        py36
+        py37
+        py38
+        py39
+        py310
+        type
+
+    [testenv]
+    deps = pytest
+    commands = pytest --import-mode importlib
+
+    [testenv:type]
+    deps = mypy
+    commands = mypy src
+"""
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index ff786503bc7690e7ee5d1d4e7b39afbf66fd7aed..0000000000000000000000000000000000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,164 +0,0 @@
-[bumpversion]
-current_version = 0.4.0
-tag = True
-commit = True
-message = release-v{new_version}
-
-[bumpversion:file:tfields/__init__.py]
-search = __version__ = '{current_version}'
-replace = {new_version}
-
-[bumpversion:file:setup.py]
-search = version='{current_version}'
-replace = {new_version}
-
-[bumpversion:file:docs/cookiecutter_input.json]
-search = 'package_version': '{current_version}'
-replace = {new_version}
-
-[metadata]
-name = tfields
-url = https://gitlab.mpcdf.mpg.de/dboe/tfields
-author = Daniel Böckenhoff
-author_email = dboe@ipp.mpg.de
-classifiers = 
-	Development Status :: 3 - Alpha
-	License :: OSI Approved :: MIT License
-	Programming Language :: Python
-	Programming Language :: Python :: 3
-	Programming Language :: Python :: 3.5
-	Programming Language :: Python :: 3.6
-	Programming Language :: Python :: 3.7
-	Programming Language :: Python :: 3.8
-	Programming Language :: Python :: 3.9
-	Programming Language :: Python :: 3.10
-license = MIT License
-license_files = LICENSE.rst
-description = Tensors, tensor fields, graphs, mesh manipulation, CAD and more on the basis of numpy.ndarrays. All objects keep track of their coordinate system. Symbolic math operations work for object manipulation.
-long_description = file: README.rst, LICENSE.rst
-long_description_content_type = text/x-rst
-keywords = tensors, tensor-fields, graphs, mesh, numpy, math
-project_urls = 
-	Documentation = https://tfields.readthedocs.io
-	Source = https://gitlab.mpcdf.mpg.de/dboe/tfields
-
-[options]
-python_requires = >=3.0
-packages = find:
-install_requires = 
-	pathlib;python_version<'3.10'
-	six
-	numpy>=1.20.0
-	sympy<=1.6.2  # diffgeom changes in 1.7 see unit branch for first implementation of compatibility
-	scipy
-	rna>=0.6.3
-	sortedcontainers
-tests_require = 
-	doctest
-	unittest
-	pytest
-
-[options.packages.find]
-exclude = 
-	tests*
-	performance*
-
-[options.extras_require]
-all = 
-	%(dev)s
-dev = 
-	%(docs)s
-    %(test)s
-	%(io)s
-	bump2version==1.0.0  # for incrementing the version
-    twine  # for publishing
-	sphinx  # for documentation
-	pre-commit  # https://pre-commit.com/ for hook managment
-	pre-commit-hooks
-	cookiecutter_project_upgrader
-docs = 
-	sphinx>=2.2.0  # requires templatedir option in sphinx-apidoc
-	sphinx_rtd_theme>=0.4.3
-test = 
-	flake8
-	pylint
-	pytest
-	pytest-cov
-	coverage
-	pytest-shutil
-	pytest-virtualenv
-	pytest-fixture-config
-	pytest-xdist
-	
-	matplotlib
-io = 
-	numpy-stl
-
-[bdist_wheel]
-universal = 1
-
-[coverage:report]
-show_missing = true
-exclude_lines = 
-	pragma: no cover
-	if False
-
-[coverage:run]
-omit = 
-	tfields/plotting/*.py
-
-[flake8]
-max-line-length = 99
-doctests = True
-exclude = .git, .eggs, __pycache__, docs, dist, venv, .tox
-ignore = E203 W503 W504  # wrong flake defaults: see https://github.com/psf/black/issues/315, https://github.com/psf/black/issues/43
-per-file-ignores = 
-	__init__.py: F401
-
-[pylint.]
-ignore = setup.py
-
-[build_sphinx]
-builder = html,man
-all-files = 1
-build-dir = docs/_build
-source-dir = docs
-
-[tool:pytest]
-addopts = 
-	--doctest-modules
-junit_family = xunit2
-
-[tox:tox]
-minversion = 3.7
-skip_missing_interpreters = true
-envlist = 
-	py{35,36,37,38,39,310}
-recreate = true
-
-[gh-actions]
-python = 
-	3.5: py35
-	3.6: py36
-	3.7: py37
-	3.8: py38
-	3.9: py39
-	3.10: py310
-
-[testenv]
-description = run test suite under {basepython}
-deps = 
-	{[options]install_requires}
-	{[options.extras_require]test}
-extras = test
-whitelist_externals = rm
-commands_pre = 
-	rm -rf **/__pycache__
-	rm -rf **/*.pyc
-commands = 
-	pytest {envsitepackagesdir}/{[metadata]name} --basetemp="{envtmpdir}" {posargs} --cov={envsitepackagesdir}/{[metadata]name} --junitxml=report/junit.xml --ignore={envsitepackagesdir}/{[metadata]name}/plotting
-
-[testenv:flake8]
-commands = flake8 {[metadata]name}/ tests/
-extras = testing
-description = run flake8 under {basepython}
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fed32f4124460da059f40a27821479e4d69c44aa..0000000000000000000000000000000000000000
--- a/setup.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from setuptools import setup
-
-setup(version="0.4.0")
diff --git a/tfields/__main__.py b/tfields/__main__.py
index 69d2eba855fef38a3207f0287a8e15c8ea72d621..f8dacf54c082119329315567ad540fdff9395c46 100644
--- a/tfields/__main__.py
+++ b/tfields/__main__.py
@@ -1,6 +1,6 @@
-#!/user/bin/env python
 """
-w7x option starter
+Main entry point for the project when run from the command line
+i.e. via $ python -m tfields
 """
 import sys
 import argparse
@@ -17,8 +17,8 @@ class SomeAction(argparse.Action):
 
     def __call__(self, parser, namespace, values, option_string=None):
         print(
-            "Example action invoked by manage in namespace: %r with values %r"
-            " and option string %r" % (namespace, values, option_string)
+            f"Example action invoked by manage in namespace: {namespace} with values {values}"
+            " and option string {option_string}."
         )
         setattr(namespace, self.dest, values)
 
diff --git a/tfields/bases/bases.py b/tfields/bases/bases.py
index 92d0529ca80d87bd57c4dd21b357e1d838ecdc44..c40d1990ed08cd1d3179d4e791713006d8a157bf 100644
--- a/tfields/bases/bases.py
+++ b/tfields/bases/bases.py
@@ -11,7 +11,6 @@ import tfields
 import numpy as np
 import sympy
 import sympy.diffgeom
-from six import string_types
 import warnings
 
 
@@ -22,7 +21,7 @@ def get_coord_system(base):
     Return:
         sympy.diffgeom.get_coord_system
     """
-    if isinstance(base, string_types) or (
+    if isinstance(base, str) or (
         isinstance(base, np.ndarray) and base.dtype.kind in {"U", "S"}
     ):
         base = getattr(tfields.bases, str(base))
@@ -45,11 +44,6 @@ def get_coord_system_name(base):
     """
     if isinstance(base, sympy.diffgeom.CoordSystem):
         base = getattr(base, "name")
-    # if not (isinstance(base, string_types) or base is None):
-    #     baseType = type(base)
-    #     raise ValueError("Coordinate system must be string_type."
-    #                      " Retrieved value '{base}' of type {baseType}."
-    #                      .format(**locals()))
     return str(base)
 
 
diff --git a/tfields/lib/util.py b/tfields/lib/util.py
index cf365652f3efe699b24abb4c923a7bb4dc3723c2..c3730a0ab2f77121e64b887784495de85a246132 100644
--- a/tfields/lib/util.py
+++ b/tfields/lib/util.py
@@ -3,7 +3,6 @@ Various utility functions
 """
 import itertools
 import typing
-from six import string_types
 import numpy as np
 
 
@@ -71,7 +70,7 @@ def flatten(seq, container=None, keep_types=None, key: typing.Callable = None):
     for item in seq:
         if (
             hasattr(item, "__iter__")
-            and not isinstance(item, string_types)
+            and not isinstance(item, str)
             and not any((isinstance(item, t) for t in keep_types))
         ):
             flatten(item, container, keep_types, key=key)
diff --git a/tools/Makefile.envs b/tools/Makefile.envs
new file mode 100644
index 0000000000000000000000000000000000000000..e789521182db7a29b26f1493ba3271b3b83ff995
--- /dev/null
+++ b/tools/Makefile.envs
@@ -0,0 +1,33 @@
+# Environment Management Makefile, adapted from https://github.com/hackalog/make_better_defaults
+#
+include tools/Makefile.include
+
+# Create the virtual environment in the process updating the lockfile. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'
+$(ENVDIR):
+	python -c "import tools; tools.env.create('$(prefix)')"
+
+# Update the environment and create it if necessary
+# Depends on
+# - changes of environment.yml
+# - existence of $(ENVDIR)
+$(ENVLOCKFILE): environment.yml $(ENVDIR)
+	python -c "import tools; tools.env.create('$(prefix)')"
+
+.PHONY: env-delete
+## Delete the virtual (conda) environment for this project. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'
+env-delete:
+	@printf $(_WARN) "env" "Really deleting the conda environment '$(prefix)'?"
+	@if $(MAKE) -s confirm ; then \
+		$(CONDA_EXE) remove --prefix $(ENVDIR) --all -y ; \
+	fi
+
+## Update poetry dependencies
+env-update-poetry: pyproject.toml
+	poetry update
+
+## Create environment if none is existing, update the environment if it is found. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'.
+env: $(ENVLOCKFILE) env-update-poetry
+
+## Use this to dynamically activate the virtual environment via `eval $(make env-activate)`. Args: prefix - name of local environment. defaults to 'env' -> 'make prefix=env_new command'.
+env-activate:
+	@echo "conda activate $(ENVDIR)"
diff --git a/tools/Makefile.help b/tools/Makefile.help
new file mode 100644
index 0000000000000000000000000000000000000000..3a39f94f836d9a2e4134a8540abe24ae36693167
--- /dev/null
+++ b/tools/Makefile.help
@@ -0,0 +1,98 @@
+#################################################################################
+# Self Documenting Help for Make Targets                                        #
+#################################################################################
+#
+# The MIT License (MIT)
+# Copyright (c) 2016 DrivenData, Inc. <https://github.com/drivendata/cookiecutter-data-science>
+# Copyright (c) 2018 Kjell Wooding <https://github.com/hackalog/easy.env>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+.DEFAULT_GOAL := help
+
+# Inspired by <http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html>
+# sed script explained:
+# /^##/:
+# 	* save line in hold space
+# 	* purge line
+# 	* Loop:
+# 		* append newline + line to hold space
+# 		* go to next line
+# 		* if line starts with doc comment, strip comment character off and loop
+# 	* remove target prerequisites
+# 	* append hold space (+ newline) to line
+# 	* replace newline plus comments by `---`
+# 	* print line
+# Separate expressions are necessary because labels cannot be delimited by
+# semicolon; see <http://stackoverflow.com/a/11799865/1968>
+.PHONY: how-help
+
+print-%  : ; @echo $* = $($*)
+
+HELP_VARS := PROJECT_NAME DEBUG_FILE ARCH
+
+help-prefix:
+	@echo "To get started:"
+	@echo "  >>> $$(tput bold)make env$$(tput sgr0)"
+	@echo "  >>> $$(tput bold)conda activate $(PROJECT_NAME)$$(tput sgr0)"
+	@echo ""
+	@echo "$$(tput bold)Project Variables:$$(tput sgr0)"
+	@echo ""
+
+help: help-prefix $(addprefix print-, $(HELP_VARS))
+	@echo
+	@echo "$$(tput bold)Available rules:$$(tput sgr0)"
+	@sed -n -e "/^## / { \
+		h; \
+		s/.*//; \
+		:doc" \
+		-e "H; \
+		n; \
+		s/^## //; \
+		t doc" \
+		-e "s/:.*//; \
+		G; \
+		s/\\n## /---/; \
+		s/\\n/ /g; \
+		p; \
+	}" ${MAKEFILE_LIST} \
+	| LC_ALL='C' sort --ignore-case \
+	| awk -F '---' \
+		-v ncol=$$(tput cols) \
+		-v indent=19 \
+		-v col_on="$$(tput setaf 6)" \
+		-v col_off="$$(tput sgr0)" \
+	'{ \
+		printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
+		n = split($$2, words, " "); \
+		line_length = ncol - indent; \
+		for (i = 1; i <= n; i++) { \
+			line_length -= length(words[i]) + 1; \
+			if (line_length <= 0) { \
+				line_length = ncol - indent - length(words[i]) - 1; \
+				printf "\n%*s ", -indent, " "; \
+			} \
+			printf "%s ", words[i]; \
+		} \
+		printf "\n"; \
+	}' \
+	| more $(shell test $(shell uname) = Darwin && echo '--no-init --raw-control-chars')
+	@echo
diff --git a/tools/Makefile.include b/tools/Makefile.include
new file mode 100644
index 0000000000000000000000000000000000000000..21c0edc09e725f70d61f5c6878da4b6c56accf3f
--- /dev/null
+++ b/tools/Makefile.include
@@ -0,0 +1,82 @@
+SHELL := /bin/bash# Use bash syntax
+
+# OPTIONS
+# part of the version string to bump
+part ?= patch
+# environment name (prefix for conda)
+prefix ?= env
+
+CURRENT_PATH := $(shell pwd -P)
+MODULE := $(shell basename "$(CURRENT_PATH)")
+VERSION :=
+ifneq (,$(shell which poetry 2>/dev/null))
+VERSION := $(shell poetry version --short)
+endif
+SOURCES := $(shell find $(MODULE) -name '*.py')
+DOCUMENTATION := $(shell find . -name '*.rst')
+GITSTATUS = $(shell git status --porcelain)
+DEBUG_FILE := debug.txt
+PROJECT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+# PYTHON_INTERPRETER := python3
+# ARCH := $(shell $(PYTHON_INTERPRETER) -c "import platform; print(platform.platform())")
+VIRTUALENV := conda
+ENVLOCKFILE := conda-linux-64.lock  # conda-$(ARCH).lock   ??? how to make compatible with conda-lock 
+ENVDIR  = $(CURRENT_PATH)/$(prefix)
+# check if the environment exists
+ENV_EXISTS := $(wildcard $(ENVDIR)/bin/activate)
+ENVACTIVE := false
+ifeq ($(CONDA_DEFAULT_ENV),$(ENVDIR))
+	ENVACTIVE = true
+endif
+CONDA_EXE := $(shell which mamba)
+ifeq (,$(CONDA_EXE))
+	CONDA_EXE := $(shell which conda)
+endif
+CONDA_RUN := 
+# set CONDA_RUN if the environment exists and is not active
+ifeq ($(ENV_EXISTS),true)
+ifeq ($(ENVACTIVE),false)
+    CONDA_RUN := conda run -p $(ENVDIR) 
+endif
+endif
+ifeq ($(shell command -v tee 2> /dev/null),)
+    REDIRECT_OUTPUT = >> $(prefix).log 2>&1
+else
+    REDIRECT_OUTPUT = | tee $(prefix).log
+endif
+DOCS_INSTALLED := false
+ifeq ($(ENVACTIVE),true)
+# check if sphinx-build and sphinx-autobuild commands are available
+ifeq ($(wildcard $(ENVDIR)/bin/sphinx-build),)
+	DOCS_INSTALLED = true
+endif
+endif
+
+
+# The CI environment variable can be set to a non-empty string,
+# it'll bypass this command that will "return true", as a "yes" answer.
+# To use the "confirm" target inside another target,
+# use the " if $(MAKE) -s confirm ; " syntax.
+confirm:
+	@if [[ -z "$(CI)" ]]; then \
+		REPLY="" ; \
+		read -p "âš  Are you sure? [y/n] > " -r ; \
+		if [[ ! $$REPLY =~ ^[Yy]$$ ]]; then \
+			printf $(_ERROR) "KO" "Stopping" ; \
+			exit 1 ; \
+		else \
+			printf $(_INFO) "OK" "Continuing" ; \
+			exit 0; \
+		fi \
+	fi
+.PHONY: confirm
+_WARN := "\033[33m[%s]\033[0m %s\n"  # Yellow text for "printf"
+_INFO := "\033[32m[%s]\033[0m %s\n" # Green text for "printf"
+_ERROR := "\033[31m[%s]\033[0m %s\n" # Red text for "printf"
+
+## install docs requirements using poetry
+requires_version:
+ifeq ($(VERSION),)
+	@printf $(_ERROR) "variables" "$(VERSION) not set - poetry not installed"
+	exit 0;
+endif
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d8baba7c8a7f089bae6a6b86366d45c5ad4d0a2
--- /dev/null
+++ b/tools/__init__.py
@@ -0,0 +1,2 @@
+from . import process
+from . import env
diff --git a/tools/env.py b/tools/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..8707bb2eb185987b712fa887f65db5b25b389ee1
--- /dev/null
+++ b/tools/env.py
@@ -0,0 +1,71 @@
+import shutil
+from .process import run_shell_command
+import tempfile
+import os
+from ensureconda.resolve import platform_subdir
+
+
+TMPDIR = tempfile.gettempdir()
+BOOTSTRAPDIR = os.path.join(TMPDIR, "bootstrap")
+PROJECT_DIR = os.path.abspath(os.path.join(__file__, "../.."))
+ENVIRONMENT_YAML = "environment.yml"
+ENVIRONMENT_YAML_PATH = os.path.join(PROJECT_DIR, ENVIRONMENT_YAML)
+POETRY_LOCK = "poetry.lock"
+CONDA_LOCK = f"conda-{platform_subdir()}.lock"
+
+
+def install(conda_run=""):
+    run_shell_command(f"{conda_run}poetry install")
+    run_shell_command(f"{conda_run}pre-commit install")
+
+
+def update(conda_run=""):
+    # Update Poetry packages and re-generate poetry.lock
+    run_shell_command(f"{conda_run}poetry update")
+    run_shell_command(f"{conda_run}pre-commit install")
+
+
+def create(name="env"):
+    """
+    This create routine was initially influenced by
+    https://stackoverflow.com/questions/70851048/does-it-make-sense-to-use-conda-poetry
+    """
+    env_dir = os.path.join(PROJECT_DIR, name)
+
+    requirements_here = ["poetry", "mamba", "conda-lock"]
+    requirements_not_satisfied = any(
+        [shutil.which(name) is None for name in requirements_here]
+    )
+
+    run_bs = ""
+    if requirements_not_satisfied:
+        # Use (and if necessary create) a bootstrap env
+        if not os.path.exists(BOOTSTRAPDIR):
+            # use python 3.10 for bootstraping
+            requirements_here.append("python==3.10")
+            run_shell_command(
+                f"conda create --yes -p {BOOTSTRAPDIR} -c conda-forge "
+                + " ".join(requirements_here)
+            )
+        run_bs = f"conda run -p {BOOTSTRAPDIR} "
+
+    # Create conda lock file(s) from environment.yml or update it if environment.yml has changed.
+    run_shell_command(
+        f"{run_bs}conda-lock --check-input-hash -k explicit --conda mamba"
+    )
+
+    run_env = f"conda run -p {env_dir} "
+    if os.path.exists(env_dir):
+        # update
+        run_shell_command(f"{run_bs}mamba update --file {CONDA_LOCK} -p {env_dir}")
+        update(conda_run=run_env)
+    else:
+        # create
+        run_shell_command(f"{run_bs}mamba create --file {CONDA_LOCK} -p {env_dir}")
+        install(conda_run=run_env)
+
+    # Add conda and poetry lock files
+    run_shell_command("git add *.lock")
+    print(
+        ">>> done. Generated lock files. You might want to run >>> git commit -m 'build(conda): lock-files'"
+    )
diff --git a/tools/process.py b/tools/process.py
new file mode 100644
index 0000000000000000000000000000000000000000..567e32a147904b9169f7c10e607ff908d6433296
--- /dev/null
+++ b/tools/process.py
@@ -0,0 +1,8 @@
+import shlex
+import subprocess
+
+
+def run_shell_command(command_line, *args, **kwargs):
+    command_line_args = shlex.split(command_line)
+    print(f"Running command '{command_line}'")
+    subprocess.call(command_line_args, *args, **kwargs)