diff --git a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/foolslide/phoenixscans/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 11bcdec79..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/foolslide/phoenixscans/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 2ec1ffe89..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 6c0af60bd..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 1043cf5c8..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index ce097eca8..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/res/web_hi_res_512.png b/multisrc/overrides/foolslide/phoenixscans/res/web_hi_res_512.png
deleted file mode 100644
index b8409ca6d..000000000
Binary files a/multisrc/overrides/foolslide/phoenixscans/res/web_hi_res_512.png and /dev/null differ
diff --git a/multisrc/overrides/foolslide/phoenixscans/src/PhoenixScans.kt b/multisrc/overrides/foolslide/phoenixscans/src/PhoenixScans.kt
deleted file mode 100644
index 8d8dfb693..000000000
--- a/multisrc/overrides/foolslide/phoenixscans/src/PhoenixScans.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package eu.kanade.tachiyomi.extension.it.phoenixscans
-
-import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
-
-class PhoenixScans : FoolSlide("The Phoenix Scans", "https://www.phoenixscans.com", "it", "/reader")
diff --git a/multisrc/overrides/pizzareader/default/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/pizzareader/default/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..9f18fceed
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/multisrc/overrides/pizzareader/default/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/pizzareader/default/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..d3ed60e90
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/multisrc/overrides/pizzareader/default/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/pizzareader/default/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..32b37d32f
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/multisrc/overrides/pizzareader/default/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/pizzareader/default/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..3bc94d763
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/multisrc/overrides/pizzareader/default/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/pizzareader/default/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..a476fb1d1
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/multisrc/overrides/pizzareader/default/res/web_hi_res_512.png b/multisrc/overrides/pizzareader/default/res/web_hi_res_512.png
new file mode 100644
index 000000000..0c7454f00
Binary files /dev/null and b/multisrc/overrides/pizzareader/default/res/web_hi_res_512.png differ
diff --git a/multisrc/overrides/pizzareader/phoenixscans/src/PhoenixScans.kt b/multisrc/overrides/pizzareader/phoenixscans/src/PhoenixScans.kt
new file mode 100644
index 000000000..260fb51fa
--- /dev/null
+++ b/multisrc/overrides/pizzareader/phoenixscans/src/PhoenixScans.kt
@@ -0,0 +1,7 @@
+package eu.kanade.tachiyomi.extension.it.phoenixscans
+
+import eu.kanade.tachiyomi.multisrc.pizzareader.PizzaReader
+
+class PhoenixScans : PizzaReader("Phoenix Scans", "https://www.phoenixscans.com", "it") {
+    override val versionId = 2
+}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt
index 6f75ee656..0495a12ac 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlideGenerator.kt
@@ -35,7 +35,6 @@ class FoolSlideGenerator : ThemeSourceGenerator {
         SingleLang("Baixar Hentai", "https://leitura.baixarhentai.net", "pt-BR", isNsfw = true, overrideVersionCode = 3),
         MultiLang("HNI-Scantrad", "https://hni-scantrad.com", listOf("fr", "en"), className = "HNIScantradFactory", pkgName = "hniscantrad", overrideVersionCode = 1),
         SingleLang("QuegnaReader", "http://pignaquegna.altervista.org", "it"),
-        SingleLang("The Phoenix Scans", "https://www.phoenixscans.com", "it", className = "PhoenixScans"),
         SingleLang("GTO The Great Site", "https://www.gtothegreatsite.net", "it", className = "GTO"),
         SingleLang("NIFTeam", "http://read-nifteam.info", "it"),
         SingleLang("TuttoAnimeManga", "https://tuttoanimemanga.net", "it"),
@@ -44,7 +43,7 @@ class FoolSlideGenerator : ThemeSourceGenerator {
         SingleLang("Mabushimajo", "http://mabushimajo.com", "tr"),
         SingleLang("Hyakuro", "https://hyakuro.com", "en"),
         SingleLang("Le Cercle du Scan", "https://lel.lecercleduscan.com", "fr", className = "LeCercleDuScan", overrideVersionCode = 1),
-        SingleLang("LetItGo Scans", "https://reader.letitgo.scans.today", "en", overrideVersionCode = 1)
+        SingleLang("LetItGo Scans", "https://reader.letitgo.scans.today", "en", overrideVersionCode = 1),
     )
 
     companion object {
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt
new file mode 100644
index 000000000..035fc4016
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReader.kt
@@ -0,0 +1,103 @@
+package eu.kanade.tachiyomi.multisrc.pizzareader
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.HttpSource
+import okhttp3.Headers
+import okhttp3.Request
+import okhttp3.Response
+import org.json.JSONObject
+import rx.Observable
+
+abstract class PizzaReader(
+    override val name: String,
+    override val baseUrl: String,
+    override val lang: String,
+    private val apiPath: String = "/api"
+) : HttpSource() {
+
+    override val supportsLatest = true
+
+    open val apiUrl by lazy { "$baseUrl$apiPath" }
+
+    override fun headersBuilder() = Headers.Builder().apply {
+        add("Referer", baseUrl)
+    }
+
+    override fun popularMangaRequest(page: Int) =
+        GET("$apiUrl/comics", headers)
+
+    override fun popularMangaParse(response: Response) =
+        MangasPage(
+            JSONObject(response.asString()).run {
+                val arr = getJSONArray("comics")
+                (0 until arr.length()).map {
+                    SManga.create().fromJSON(arr.getJSONObject(it))
+                }
+            },
+            false
+        )
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
+        GET("$apiUrl/search/$query", headers)
+
+    override fun searchMangaParse(response: Response) =
+        MangasPage(
+            JSONObject(response.asString()).run {
+                val arr = getJSONArray("comics")
+                (0 until arr.length()).map {
+                    SManga.create().fromJSON(arr.getJSONObject(it))
+                }
+            },
+            false
+        )
+
+    // TODO
+    override fun latestUpdatesRequest(page: Int): Request =
+        throw UnsupportedOperationException("Not used")
+
+    override fun latestUpdatesParse(response: Response): MangasPage =
+        throw UnsupportedOperationException("Not used")
+
+    // Workaround to allow "Open in browser" to use the real URL
+    override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
+        client.newCall(chapterListRequest(manga)).asObservableSuccess()
+            .map { mangaDetailsParse(it).apply { initialized = true } }
+
+    // Return the real URL for "Open in browser"
+    override fun mangaDetailsRequest(manga: SManga) =
+        GET("$baseUrl${manga.url}", headers)
+
+    override fun mangaDetailsParse(response: Response): SManga =
+        SManga.create().fromJSON(JSONObject(response.asString()).getJSONObject("comic"))
+
+    override fun chapterListRequest(manga: SManga) = GET("$apiUrl${manga.url}", headers)
+
+    override fun chapterListParse(response: Response) =
+        JSONObject(response.asString()).getJSONObject("comic").run {
+            val arr = getJSONArray("chapters")
+            (0 until arr.length()).map {
+                SChapter.create().fromJSON(arr.getJSONObject(it))
+            }
+        }
+
+    override fun pageListRequest(chapter: SChapter) =
+        GET("$apiUrl${chapter.url}", headers)
+
+    override fun pageListParse(response: Response) =
+        JSONObject(response.asString()).getJSONObject("chapter").run {
+            val arr = getJSONArray("pages")
+            (0 until arr.length()).map {
+                Page(it, "", arr.getString(it))
+            }
+        }
+
+    override fun imageUrlParse(response: Response): String =
+        throw UnsupportedOperationException("Not used")
+
+}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt
new file mode 100644
index 000000000..ff5fd54cc
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderExtensions.kt
@@ -0,0 +1,94 @@
+package eu.kanade.tachiyomi.multisrc.pizzareader
+
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import okhttp3.Response
+import org.json.JSONArray
+import org.json.JSONObject
+import java.text.DecimalFormat
+// import java.time.OffsetDateTime
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+/** Returns the body of a response as a `String`. */
+fun Response.asString(): String = body!!.string()
+
+/**
+ * Formats the number according to [fmt].
+ *
+ * @param fmt A [DecimalFormat] string.
+ * @return A string representation of the number.
+ */
+fun Number.format(fmt: String): String = DecimalFormat(fmt).format(this)
+
+/**
+ * Joins each value of a given [field] of the array using [sep].
+ *
+ * @param field The index of a [JSONArray].
+ * When its type is [String], it is treated as the key of a [JSONObject].
+ * @param sep The separator used to join the array.
+ * @return The joined string, or `null` if the array is empty.
+ */
+fun JSONArray.joinField(field: Int, sep: String = ", ") =
+    length().takeIf { it != 0 }?.run {
+        (0 until this).mapNotNull {
+            val obj = get(it)
+            if (obj != null && obj.toString() != "null") getJSONArray(it).getString(field)
+            else null
+        }.joinToString(sep)
+    }
+
+/**
+ * Joins each value of a given [field] of the array using [sep].
+ *
+ * @param field The key of a [JSONObject].
+ * @param sep The separator used to join the array.
+ * @return The joined string, or `null` if the array is empty.
+ */
+fun JSONArray.joinField(field: String, sep: String = ", ") =
+    length().takeIf { it != 0 }?.run {
+        (0 until this).mapNotNull {
+            val obj = get(it)
+            if (obj != null && obj.toString() != "null") getJSONObject(it).getString(field)
+            else null
+        }.joinToString(sep)
+    }
+
+/**
+ * Creates a [SManga] by parsing a [JSONObject].
+ *
+ * @param obj The object containing the manga info.
+ */
+fun SManga.fromJSON(obj: JSONObject) = apply {
+    url = obj.getString("url")
+    title = obj.getString("title")
+    description = obj.getString("description")
+    thumbnail_url = obj.getString("thumbnail")
+    author = obj.getString("author")
+    artist = obj.getString("artist")
+    genre = obj.getJSONArray("genres").joinField("slug")
+    status = when (obj.getString("status").substring(0, 7)) {
+        "In cors" -> SManga.ONGOING
+        "On goin" -> SManga.ONGOING
+        "Complet" -> SManga.COMPLETED
+        "Conclus" -> SManga.COMPLETED
+        "Conclud" -> SManga.COMPLETED
+        "Licenzi" -> SManga.LICENSED
+        "License" -> SManga.LICENSED
+        else -> SManga.UNKNOWN
+    }
+}
+
+/**
+ * Creates a [SChapter] by parsing a [JSONObject].
+ *
+ * @param obj The object containing the chapter info.
+ */
+fun SChapter.fromJSON(obj: JSONObject) = apply {
+    url = obj.getString("url")
+    chapter_number = obj.optString("chapter", "-1").toFloat() + "0.${obj.optInt("subchapter", 0)}".toFloat()
+    // date_upload = OffsetDateTime.parse(obj.getString("published_on")).toEpochSecond()  // API 26
+    date_upload = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ITALY).parse(obj.getString("published_on"))?.time ?: 0L
+    scanlator = obj.getJSONArray("teams").joinField("name", " & ")
+    name = obj.getString("full_title")
+}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt
new file mode 100644
index 000000000..718f4f3ce
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/pizzareader/PizzaReaderGenerator.kt
@@ -0,0 +1,27 @@
+package eu.kanade.tachiyomi.multisrc.pizzareader
+
+// import generator.ThemeSourceData.MultiLang
+import generator.ThemeSourceData.SingleLang
+import generator.ThemeSourceGenerator
+
+class PizzaReaderGenerator : ThemeSourceGenerator {
+
+    override val themePkg = "pizzareader"
+
+    override val themeClass = "PizzaReader"
+
+    override val baseVersionCode: Int = 0
+
+    override val sources = listOf(
+        SingleLang("Phoenix Scans", "https://www.phoenixscans.com", "it", className = "PhoenixScans", overrideVersionCode = 4),
+        // Current migrating to this CMS:
+        // SingleLang("GTO The Great Site", "https://reader.gtothegreatsite.net", "it", className = "GTO", overrideVersionCode = 4),
+    )
+
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+            PizzaReaderGenerator().createAll()
+        }
+    }
+}