diff --git a/libc3_web/Makefile b/libc3_web/Makefile
new file mode 100644
index 0000000..ea5d40c
--- /dev/null
+++ b/libc3_web/Makefile
@@ -0,0 +1,81 @@
+## c3
+## Copyright 2022-2024 kmx.io <contact@kmx.io>
+##
+## Permission is hereby granted to use this software granted the above
+## copyright notice and this permission paragraph are included in all
+## copies and substantial portions of this software.
+##
+## THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
+## PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
+## AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
+## THIS SOFTWARE.
+
+CLEANFILES = *.a *.gcno *.la .libs *.lo *.o
+
+CLEANFILES_COV = *.css *.gcda *.html .libs/*.gcda
+
+CLEANFILES += ${CLEANFILES_COV}
+
+DISTCLEANFILES = .build ${CLEANFILES} config.h config.mk
+
+all:
+ ${MAKE} build
+ ${MAKE} debug
+ if ${HAVE_ASAN}; then ${MAKE} asan; fi
+ if ${HAVE_GCOV}; then ${MAKE} cov; fi
+
+asan:
+ ${MAKE} ${LIB_ASAN}
+
+build:
+ ${MAKE} ${LIB}
+
+clean:
+ rm -rf ${CLEANFILES}
+
+clean_cov:
+ rm -rf ${CLEANFILES_COV}
+
+cov:
+ ${MAKE} ${LIB_COV}
+
+debug:
+ ${MAKE} ${LIB_DEBUG}
+
+distclean:
+ rm -rf ${DISTCLEANFILES}
+
+gcovr:
+ gcovr --gcov-executable ${GCOV} --html-details libc3_web.html
+
+include gen.mk
+CLEANFILES += ${GENERATED_FILES}
+
+install:
+ ${INSTALL} -o ${OWNER} -g ${GROUP} -m 0755 -d ${prefix}/include/libc3_web
+ ${LIBTOOL} --tag=CC --mode=install ${INSTALL} -o ${OWNER} -g ${GROUP} -m 0644 ${HEADERS} ${prefix}/include/libc3/web
+ ${INSTALL} -o ${OWNER} -g ${GROUP} -m 0755 -d ${prefix}/lib
+ ${LIBTOOL} --tag=CC --mode=install ${INSTALL} -o ${OWNER} -g ${GROUP} -m 0644 ${LIB} ${prefix}/lib
+ ${LIBTOOL} --tag=CC --mode=install ${INSTALL} -o ${OWNER} -g ${GROUP} -m 0644 ${LIB_DEBUG} ${prefix}/lib
+ ${LIBTOOL} --finish ${prefix}/lib
+
+test:
+
+update_sources:
+ ./update_sources
+
+.PHONY: \
+ all \
+ asan \
+ build \
+ clean \
+ cov \
+ debug \
+ distclean \
+ gen \
+ install \
+ test \
+ update_sources \
+
+include config.mk
+include sources.mk
diff --git a/libc3_web/configure b/libc3_web/configure
new file mode 100755
index 0000000..13a34dc
--- /dev/null
+++ b/libc3_web/configure
@@ -0,0 +1,101 @@
+#!/bin/sh
+## c3
+## Copyright 2022-2024 kmx.io <contact@kmx.io>
+##
+## Permission is hereby granted to use this software granted the above
+## copyright notice and this permission paragraph are included in all
+## copies and substantial portions of this software.
+##
+## THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
+## PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
+## AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
+## THIS SOFTWARE.
+
+set -e
+
+export SRC_TOP="$(dirname "$PWD")"
+CONFIG_H_PREFIX=LIBC3_WEB_
+
+. ../config.subr
+
+LIB=libc3_web.la
+LIB_ASAN=libc3_web_asan.la
+LIB_COV=libc3_web_cov.la
+LIB_DEBUG=libc3_web_debug.la
+
+echo "LIB = $LIB" >> ${CONFIG_MK}
+echo "LIB_ASAN = $LIB_ASAN" >> ${CONFIG_MK}
+echo "LIB_COV = $LIB_COV" >> ${CONFIG_MK}
+echo "LIB_DEBUG = $LIB_DEBUG" >> ${CONFIG_MK}
+
+. ./sources.sh
+
+OBJECTS="$(c2ext .main.lo "$SOURCES")"
+echo "OBJECTS = $OBJECTS" >> ${CONFIG_MK}
+OBJECTS_ASAN="$(c2ext .asan.lo "$SOURCES")"
+OBJECTS_COV="$(c2ext .cov.lo "$SOURCES")"
+OBJECTS_DEBUG="$(c2ext .debug.lo "$SOURCES")"
+
+# Common config for all targets
+CFLAGS="$CFLAGS -W -Wall -Werror -std=c11 -pedantic -pipe"
+CFLAGS="$CFLAGS -msse2 -mfpmath=sse"
+LDFLAGS="-export-dynamic $LDFLAGS -rdynamic"
+config_asan
+config_gnu
+config_define PREFIX "\"${PREFIX}\""
+update_config_h
+LIBS="$LIBS -lm -rpath ${PREFIX}/lib"
+
+# Address Sanitizer config
+CFLAGS_ASAN="$CFLAGS -DDEBUG -O1 -g"
+CFLAGS_ASAN="$CFLAGS_ASAN -fsanitize=address -fno-omit-frame-pointer"
+LDFLAGS_ASAN="$LDFLAGS"
+LIBS_ASAN="$LIBS"
+
+# Coverage config
+CFLAGS_COV="$CFLAGS -fprofile-arcs -ftest-coverage"
+LDFLAGS_COV="$LDFLAGS --coverage"
+LIBS_COV="$LIBS -lgcov"
+
+# Debug config
+CFLAGS_DEBUG="$CFLAGS -DDEBUG -O0 -g"
+LDFLAGS_DEBUG="$LDFLAGS"
+LIBS_DEBUG="$LIBS"
+
+# Main config
+DEFAULT_CFLAGS="-O2 -fPIC"
+if [ "x$ENV_CFLAGS" = "x" ]; then
+ CFLAGS="$CFLAGS $DEFAULT_CFLAGS"
+fi
+CFLAGS="$CFLAGS -DNDEBUG"
+LIBS="$LIBS"
+
+echo "LIB = $LIB" >> ${CONFIG_MK}
+echo "HAVE_ASAN = $HAVE_ASAN" >> ${CONFIG_MK}
+echo "CPPFLAGS = $CPPFLAGS" >> ${CONFIG_MK}
+echo "CFLAGS = $CFLAGS" >> ${CONFIG_MK}
+echo "LDFLAGS = $LDFLAGS" >> ${CONFIG_MK}
+echo "LIBS = $LIBS" >> ${CONFIG_MK}
+echo >> ${CONFIG_MK}
+echo "LIB_ASAN = $LIB_ASAN" >> ${CONFIG_MK}
+echo "CFLAGS_ASAN = $CFLAGS_ASAN" >> ${CONFIG_MK}
+echo "LDFLAGS_ASAN = $LDFLAGS_ASAN" >> ${CONFIG_MK}
+echo "LIBS_ASAN = $LIBS_ASAN" >> ${CONFIG_MK}
+echo >> ${CONFIG_MK}
+echo "LIB_COV = $LIB_COV" >> ${CONFIG_MK}
+echo "CFLAGS_COV = $CFLAGS_COV" >> ${CONFIG_MK}
+echo "LDFLAGS_COV = $LDFLAGS_COV" >> ${CONFIG_MK}
+echo "LIBS_COV = $LIBS_COV" >> ${CONFIG_MK}
+echo >> ${CONFIG_MK}
+echo "LIB_DEBUG = $LIB_DEBUG" >> ${CONFIG_MK}
+echo "CFLAGS_DEBUG = $CFLAGS_DEBUG" >> ${CONFIG_MK}
+echo "LDFLAGS_DEBUG = $LDFLAGS_DEBUG" >> ${CONFIG_MK}
+echo "LIBS_DEBUG = $LIBS_DEBUG" >> ${CONFIG_MK}
+
+update_build
+update_build_lib
+
+build_lo
+build_lib
+
+update_config_mk
diff --git a/libc3_web/ec3.c b/libc3_web/ec3.c
new file mode 100644
index 0000000..e7644ae
--- /dev/null
+++ b/libc3_web/ec3.c
@@ -0,0 +1,55 @@
+/* c3
+ * Copyright 2022-2024 kmx.io <contact@kmx.io>
+ *
+ * Permission is hereby granted to use this software granted the above
+ * copyright notice and this permission paragraph are included in all
+ * copies and substantial portions of this software.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
+ * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
+ * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+#include "libc3/c3.h"
+#include "ec3.h"
+
+sw ec3_buf_parse (s_ec3 *ec3, s_buf *buf)
+{
+ assert(ec3);
+ assert(buf);
+ (void) ec3;
+ (void) buf;
+ return -42;
+}
+
+void ec3_clean (s_ec3 *ec3)
+{
+ list_clean(ec3);
+}
+
+s_ec3 * ec3_init (s_ec3 *ec3)
+{
+ s_ec3 tmp = {0};
+ *ec3 = tmp;
+ return ec3;
+}
+
+sw ec3_render (const s_ec3 *ec3, s_buf *buf, s_map *map)
+{
+ assert(ec3);
+ assert(buf);
+ assert(map);
+ (void) ec3;
+ (void) buf;
+ (void) map;
+ return -42;
+}
+
+s_fn * ec3_to_render_fn (const s_ec3 *ec3, s_fn *dest)
+{
+ assert(ec3);
+ assert(dest);
+ (void) ec3;
+ (void) dest;
+ return NULL;
+}
diff --git a/libc3_web/ec3.h b/libc3_web/ec3.h
new file mode 100644
index 0000000..ba122ec
--- /dev/null
+++ b/libc3_web/ec3.h
@@ -0,0 +1,29 @@
+/* c3
+ * Copyright 2022-2024 kmx.io <contact@kmx.io>
+ *
+ * Permission is hereby granted to use this software granted the above
+ * copyright notice and this permission paragraph are included in all
+ * copies and substantial portions of this software.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
+ * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
+ * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+#ifndef LIBC3_WEB_EC3_H
+#define LIBC3_WEB_EC3_H
+
+#include "types.h"
+
+/* Stack-allocation compatible functions, call ec3_clean after use. */
+void ec3_clean (s_ec3 *ec3);
+s_ec3 * ec3_init (s_ec3 *ec3);
+
+/* Operators. */
+sw ec3_buf_parse (s_ec3 *ec3, s_buf *buf);
+
+/* Observers. */
+sw ec3_render (const s_ec3 *ec3, s_buf *buf, s_map *map);
+s_fn * ec3_to_render_fn (const s_ec3 *ec3, s_fn *dest);
+
+#endif /* LIBC3_WEB_EC3_H */
diff --git a/libc3_web/update_sources b/libc3_web/update_sources
new file mode 100755
index 0000000..b313326
--- /dev/null
+++ b/libc3_web/update_sources
@@ -0,0 +1,26 @@
+#!/bin/sh
+## c3
+## Copyright 2022-2024 kmx.io <contact@kmx.io>
+##
+## Permission is hereby granted to use this software granted the above
+## copyright notice and this permission paragraph are included in all
+## copies and substantial portions of this software.
+##
+## THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
+## PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
+## AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
+## THIS SOFTWARE.
+
+. ../config.subr
+
+echo "# sources.mk generated by update_sources" > ${SOURCES_MK}
+echo "# sources.sh generated by update_sources" > ${SOURCES_SH}
+
+HEADERS="$(ls *.h | sort)"
+sources HEADERS "$HEADERS"
+
+SOURCES="$(ls *.c | sort)"
+sources SOURCES "$SOURCES"
+
+update_sources_mk
+update_sources_sh