diff --git a/mosogepsch/src/jsMain/kotlin/Main.kt b/mosogepsch/src/jsMain/kotlin/Main.kt
index b9548d96e7ad1f633928d8f40c86f4a2e73337da..00ef8be901a7d35adfb60c6dbd81beaae129a442 100644
--- a/mosogepsch/src/jsMain/kotlin/Main.kt
+++ b/mosogepsch/src/jsMain/kotlin/Main.kt
@@ -1,4 +1,5 @@
 import androidx.compose.runtime.*
+import api.MachineKind
 import app.softwork.bootstrapcompose.*
 import app.softwork.bootstrapcompose.Color
 import components.*
@@ -12,6 +13,7 @@ import org.jetbrains.compose.web.renderComposable
 import org.w3c.dom.HTMLDivElement
 import org.w3c.dom.get
 import org.w3c.dom.set
+import styles.FloorStyle
 import styles.LocalStyle
 import styles.Style
 
@@ -47,7 +49,23 @@ fun main() {
                     DomSideEffect {
                         it.setImportantBg(mosogepStyle.colors.surface)
                     }
-                    Text("MosógépSCH")
+                    Div(
+                        attrs = {
+                            style {
+                                display(DisplayStyle.Flex)
+                                flexDirection(FlexDirection.Row)
+                                gap(.5.em)
+                                alignItems(AlignItems.Center)
+                            }
+                        }
+                    ) {
+                        icon(
+                            src = MachineKind.Washer.toIcon(),
+                            color = mosogepStyle.colors.onSurface,
+                            width = 1.5.em
+                        )
+                        Text("MosógépSCH")
+                    }
                     switch(
                         label = "Sötét téma",
                         value = mosogepStyle.dark,
diff --git a/mosogepsch/src/jsMain/kotlin/components/Floor.kt b/mosogepsch/src/jsMain/kotlin/components/Floor.kt
index 51a0a8770e498ade13d1d8e806f12b51cd22f743..068dac26a8a60d3e533f6ce00030f4ab090cc552 100644
--- a/mosogepsch/src/jsMain/kotlin/components/Floor.kt
+++ b/mosogepsch/src/jsMain/kotlin/components/Floor.kt
@@ -1,104 +1,14 @@
 package components
 
-import styles.LocalStyle
 import androidx.compose.runtime.*
 import api.Floor
 import api.Machine
-import api.MachineKind
-import api.MachineStatus
-import localization.LocalLang
-import localization.machineKind
-import localization.machineStatus
 import org.jetbrains.compose.web.css.*
 import org.jetbrains.compose.web.dom.*
-import styles.ColorPair
-import styles.applyColorPair
-
-object FloorStyle: StyleSheet() {
-    val container by style {
-        display(DisplayStyle.Flex)
-        flexDirection(FlexDirection.Column)
-        justifyContent(JustifyContent.SpaceAround)
-        alignItems(AlignItems.Center)
-        position(Position.Relative)
-    }
-
-    val card by style {
-        maxWidth(20.em)
-        width(20.em)
-        flexDirection(FlexDirection.Row)
-        justifyContent(JustifyContent.SpaceAround)
-        property("box-shadow", "0 0 .7em #00000033")
-    }
-
-    val floorNum by style {
-        minWidth(2.75.ch)
-        marginBottom(0.px)
-        textAlign("right")
-    }
-
-    val machine by style {
-        flexGrow(1)
-        cursor("pointer")
-    }
-
-    val absCnt by style {
-        position(Position.Absolute)
-        left(0.px)
-        bottom(4.em)
-        width(0.px)
-        height(0.px)
-        property("z-index", -1)
-    }
-    val relCnt by style {
-        position(Position.Relative)
-        width(0.px)
-        height(0.px)
-    }
-    val underflowCnt by style {
-        margin(0.px)
-        padding(1.em)
-        paddingTop(0.em)
-        width(22.em)
-        overflow("hidden")
-        property("transition", "${styles.Style.baseTransition}, max-height 0.75s")
-    }
-    val underflowCard by style {
-        justifyContent(JustifyContent.SpaceBetween)
-        flexDirection(FlexDirection.Row)
-        gap(1.5.em)
-        padding(1.5.em)
-        margin(0.px)
-        width(100.percent)
-        paddingTop(5.em)
-        property("transition", "${styles.Style.baseTransition}, box-shadow 0.5s")
-        property("box-shadow", "0 0 1em #00000044")
-    }
-    val infoDiv by style {
-        display(DisplayStyle.Flex)
-        flexDirection(FlexDirection.Column)
-        flexGrow(1)
-    }
-}
+import styles.FloorStyle
 
 @Composable
 fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) {
-
-    val lang = LocalLang.current
-    val colors = LocalStyle.current.colors
-    fun MachineStatus.toColor(): ColorPair = when (this) {
-        MachineStatus.Available -> ColorPair(colors.primary, colors.onPrimary)
-        MachineStatus.NotAvailable -> ColorPair(colors.error, colors.onError)
-    }
-    fun MachineStatus.toUnderColor(): ColorPair = when (this) {
-        MachineStatus.Available -> ColorPair(colors.primaryContainer, colors.onPrimaryContainer)
-        MachineStatus.NotAvailable -> ColorPair(colors.errorContainer, colors.onErrorContainer)
-    }
-    fun MachineKind.toIcon(): String = when (this) {
-        MachineKind.Washer -> "washer.svg"
-        MachineKind.Dryer -> "fan.svg"
-    }
-
     val zIndex = 200 - floor.id*2
 
     Div(attrs = {
@@ -107,43 +17,7 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
             property("z-index", zIndex)
         }
     }) {
-        card(attrs = {
-            classes(FloorStyle.card)
-            style {
-                backgroundColor(colors.surfaceVariant)
-                color(colors.onSurfaceVariant)
-            }
-        }) {
-            H3(attrs = {
-                classes(FloorStyle.floorNum)
-            }) { Text("${floor.id}.") }
-            floor.machines.forEach { machine ->
-                card(attrs = {
-                    classes(FloorStyle.machine)
-                    style {
-                        applyColorPair(machine.status.toColor())
-                    }
-                    onClick {
-                        if (selectedMachine == machine) {
-                            setMachine(null)
-                        } else {
-                            setMachine(machine)
-                        }
-                    }
-                }) {
-                    Div(
-                        attrs = {
-                            style {
-                                backgroundColor(machine.status.toColor().fg)
-                                property("mask", "url(/${machine.kindOf.toIcon()}) no-repeat center / contain")
-                                height(2.em)
-                                width(2.em)
-                            }
-                        }
-                    ) {}
-                }
-            }
-        }
+        FloorCard(floor, selectedMachine, setMachine)
 
         floor.machines.forEach { machine ->
             Div(attrs = {
@@ -156,39 +30,13 @@ fun floor(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit)
                         classes(FloorStyle.underflowCnt)
                         style {
                             if (selectedMachine == machine) {
-                                maxHeight(20.em)
+                                maxHeight(22.em)
                             } else {
                                 maxHeight(0.px)
                             }
                         }
                     }) {
-                        card(attrs = {
-                            classes(FloorStyle.underflowCard)
-                            style {
-                                applyColorPair(machine.status.toUnderColor())
-                                if (selectedMachine != machine) {
-                                    property("box-shadow", "none")
-                                }
-                            }
-                        }) {
-                            Div(
-                                attrs = {
-                                    style {
-                                        backgroundColor(machine.status.toUnderColor().fg)
-                                        property("mask", "url(/${machine.kindOf.toIcon()}) no-repeat center / contain")
-                                        height(2.em)
-                                        width(2.em)
-                                    }
-                                }
-                            ) {}
-                            Div(attrs = {
-                                classes(FloorStyle.infoDiv)
-                            }) {
-                                H5 { Text("${lang.machineKind(machine.kindOf)} ${lang.floorFormat(floor.id)}") }
-                                P { Text("${lang.lastUpdated} ${lang.formatInstant(machine.lastQueryTime)}") }
-                                P { Text(lang.machineStatus(machine.status)) }
-                            }
-                        }
+                        underCard(floor, machine, selectedMachine)
                     }
                 }
             }
diff --git a/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt b/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b37c80aaf3e6935cf387511473f2cab17f2ca0e6
--- /dev/null
+++ b/mosogepsch/src/jsMain/kotlin/components/FloorCard.kt
@@ -0,0 +1,50 @@
+package components
+
+import androidx.compose.runtime.Composable
+import api.Floor
+import api.Machine
+import org.jetbrains.compose.web.css.*
+import org.jetbrains.compose.web.dom.Div
+import org.jetbrains.compose.web.dom.H3
+import org.jetbrains.compose.web.dom.Text
+import styles.FloorStyle
+import styles.LocalStyle
+import styles.applyColorPair
+
+@Composable
+fun FloorCard(floor: Floor, selectedMachine: Machine?, setMachine: (Machine?)->Unit) {
+    val colors = LocalStyle.current.colors
+    card(attrs = {
+        classes(FloorStyle.card)
+        style {
+            backgroundColor(colors.surfaceVariant)
+            color(colors.onSurfaceVariant)
+        }
+    }) {
+        H3(attrs = {
+            classes(FloorStyle.floorNum)
+        }) { Text("${floor.id}.") }
+        floor.machines.forEach { machine ->
+            val cardColor = machine.status.toColor()
+            card(attrs = {
+                classes(FloorStyle.machine)
+                style {
+                    applyColorPair(cardColor)
+                }
+                onClick {
+                    if (selectedMachine == machine) {
+                        setMachine(null)
+                    } else {
+                        setMachine(machine)
+                    }
+                }
+            }) {
+                icon(
+                    src = machine.kindOf.toIcon(),
+                    color = cardColor.fg,
+                    width = 2.em
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/mosogepsch/src/jsMain/kotlin/components/Icon.kt b/mosogepsch/src/jsMain/kotlin/components/Icon.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cb14beb3eed237175d99c826d63fb94640d7957b
--- /dev/null
+++ b/mosogepsch/src/jsMain/kotlin/components/Icon.kt
@@ -0,0 +1,25 @@
+package components
+
+import androidx.compose.runtime.Composable
+import org.jetbrains.compose.web.css.*
+import org.jetbrains.compose.web.dom.Div
+
+@Composable
+fun icon(
+    src: String,
+    color: CSSColorValue,
+    width: CSSNumeric,
+    height: CSSNumeric = width,
+) {
+    Div(
+        attrs = {
+            style {
+                backgroundColor(color)
+                property("mask", "url($src) no-repeat center / contain")
+                property("-webkit-mask", "url($src) no-repeat center / contain")
+                height(height)
+                width(width)
+            }
+        }
+    ) {}
+}
\ No newline at end of file
diff --git a/mosogepsch/src/jsMain/kotlin/components/UnderCard.kt b/mosogepsch/src/jsMain/kotlin/components/UnderCard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..aa675675ff8c87425be0e8341c86493800f7d609
--- /dev/null
+++ b/mosogepsch/src/jsMain/kotlin/components/UnderCard.kt
@@ -0,0 +1,50 @@
+package components
+
+import androidx.compose.runtime.*
+import api.Floor
+import api.Machine
+import localization.LocalLang
+import localization.machineKind
+import localization.machineStatus
+import org.jetbrains.compose.web.dom.H5
+import org.jetbrains.compose.web.dom.P
+import org.jetbrains.compose.web.dom.Text
+import styles.ColorPair
+import styles.FloorStyle
+import styles.LocalStyle
+import styles.applyColorPair
+
+
+
+@Composable
+fun underCard(floor: Floor, machine: Machine, selectedMachine: Machine?) {
+    val lang = LocalLang.current
+    val cardColor = machine.status.toUnderColor()
+    val subbedColor = subbedColor()
+    val unsubbedColor = unsubbedColor()
+
+    card(attrs = {
+        classes(FloorStyle.underflowCard)
+        style {
+            applyColorPair(cardColor)
+            if (selectedMachine != machine) {
+                property("box-shadow", "none")
+            }
+        }
+    }) {
+        H5 { Text("${lang.machineKind(machine.kindOf)} ${lang.floorFormat(floor.id)}") }
+        P { Text("${lang.lastUpdated} ${lang.formatInstant(machine.lastQueryTime)}") }
+        P { Text(lang.machineStatus(machine.status)) }
+        var subbed by remember { mutableStateOf(false) }
+        card(attrs = {
+            classes(FloorStyle.subBtn)
+            onClick { subbed = !subbed }
+            style {
+                applyColorPair(if (subbed) subbedColor else unsubbedColor)
+            }
+        }) {
+            // TODO place icon
+            Text(if (subbed) lang.unsub else lang.sub)
+        }
+    }
+}
\ No newline at end of file
diff --git a/mosogepsch/src/jsMain/kotlin/components/Utils.kt b/mosogepsch/src/jsMain/kotlin/components/Utils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..187121d7231e2e671919b819f3a3c0953d496b32
--- /dev/null
+++ b/mosogepsch/src/jsMain/kotlin/components/Utils.kt
@@ -0,0 +1,44 @@
+package components
+
+import androidx.compose.runtime.Composable
+import api.MachineKind
+import api.MachineStatus
+import styles.ColorPair
+import styles.LocalStyle
+
+@Composable
+fun MachineStatus.toColor(): ColorPair {
+    val colors = LocalStyle.current.colors
+    return when (this) {
+        MachineStatus.Available -> ColorPair(colors.primary, colors.onPrimary)
+        MachineStatus.NotAvailable -> ColorPair(colors.error, colors.onError)
+    }
+}
+
+@Composable
+fun MachineStatus.toUnderColor(): ColorPair {
+    val colors = LocalStyle.current.colors
+    return when (this) {
+        MachineStatus.Available -> ColorPair(colors.primaryContainer, colors.onPrimaryContainer)
+        MachineStatus.NotAvailable -> ColorPair(colors.errorContainer, colors.onErrorContainer)
+    }
+}
+
+fun MachineKind.toIcon(): String {
+    return when (this) {
+        MachineKind.Washer -> "washer.svg"
+        MachineKind.Dryer -> "fan.svg"
+    }
+}
+
+@Composable
+fun subbedColor(): ColorPair {
+    val colors = LocalStyle.current.colors
+    return ColorPair(colors.error, colors.onError)
+}
+
+@Composable
+fun unsubbedColor(): ColorPair {
+    val colors = LocalStyle.current.colors
+    return ColorPair(colors.primary, colors.onPrimary)
+}
\ No newline at end of file
diff --git a/mosogepsch/src/jsMain/kotlin/localization/English.kt b/mosogepsch/src/jsMain/kotlin/localization/English.kt
index 73d1aea45da0f4213c422a97d0f8194f0698fe2c..c5bb88ad8e82af5b7eec468c3d7531c3a9b25453 100644
--- a/mosogepsch/src/jsMain/kotlin/localization/English.kt
+++ b/mosogepsch/src/jsMain/kotlin/localization/English.kt
@@ -17,6 +17,9 @@ abstract class Localization {
     open val lastUpdated = "Last updated"
     open val daysAgo = "days ago"
 
+    open val sub = "Subscribe"
+    open val unsub = "Unsubscribe"
+
     protected open fun timeFormat(dateTime: LocalDateTime): String = "at ${dateTime.hour.padded()}:${dateTime.minute.padded()}"
 
     open fun floorFormat(floor: Int) = "on floor $floor"
@@ -26,7 +29,6 @@ abstract class Localization {
         val dateTime = instant.toLocalDateTime(tz)
         val now = Clock.System.now()
         val diff = now - instant
-        println(diff.toString())
         if (diff < Duration.Companion.days(1)) {
             return timeFormat(dateTime)
         }
diff --git a/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt
index 12b632d5dc5bda4a801d9f120f5a28ef75226e6f..f91015df24095f55e88dceb19532251a67a1a9a5 100644
--- a/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt
+++ b/mosogepsch/src/jsMain/kotlin/localization/Hungarian.kt
@@ -12,6 +12,9 @@ class Hungarian: Localization() {
     override val lastUpdated = "Utoljára frissítve"
     override val daysAgo = "napja"
 
+    override val sub = "Feliratkozás"
+    override val unsub = "Leiratkozás"
+
     override fun timeFormat(dateTime: LocalDateTime): String = "${dateTime.hour.padded()}:${dateTime.minute.padded()}-kor"
     override fun floorFormat(floor: Int): String {
         val nevelo = when(floor) {
diff --git a/mosogepsch/src/jsMain/kotlin/styles/FloorStyle.kt b/mosogepsch/src/jsMain/kotlin/styles/FloorStyle.kt
new file mode 100644
index 0000000000000000000000000000000000000000..58e67894a7af66f86ee390f536ee0859a50e8ec3
--- /dev/null
+++ b/mosogepsch/src/jsMain/kotlin/styles/FloorStyle.kt
@@ -0,0 +1,68 @@
+package styles
+
+import org.jetbrains.compose.web.css.*
+
+object FloorStyle: StyleSheet() {
+    val container by style {
+        display(DisplayStyle.Flex)
+        flexDirection(FlexDirection.Column)
+        justifyContent(JustifyContent.SpaceAround)
+        alignItems(AlignItems.Center)
+        position(Position.Relative)
+    }
+
+    val card by style {
+        maxWidth(20.em)
+        width(20.em)
+        flexDirection(FlexDirection.Row)
+        justifyContent(JustifyContent.SpaceAround)
+        property("box-shadow", "0 0 .7em #00000033")
+    }
+
+    val floorNum by style {
+        minWidth(2.75.ch)
+        marginBottom(0.px)
+        textAlign("right")
+    }
+
+    val machine by style {
+        flexGrow(1)
+        cursor("pointer")
+    }
+
+    val absCnt by style {
+        position(Position.Absolute)
+        left(0.px)
+        bottom(4.em)
+        width(0.px)
+        height(0.px)
+        property("z-index", -1)
+    }
+    val relCnt by style {
+        position(Position.Relative)
+        width(0.px)
+        height(0.px)
+    }
+    val underflowCnt by style {
+        margin(0.px)
+        padding(1.em)
+        paddingTop(0.em)
+        width(22.em)
+        overflow("hidden")
+        property("transition", "${Style.baseTransition}, max-height 0.75s")
+    }
+    val underflowCard by style {
+        flexDirection(FlexDirection.Column)
+        justifyContent(JustifyContent.FlexStart)
+        alignItems(AlignItems.Stretch)
+        padding(1.5.em)
+        margin(0.px)
+        width(100.percent)
+        paddingTop(5.em)
+        property("transition", "${Style.baseTransition}, box-shadow 0.5s")
+        property("box-shadow", "0 0 1em #00000044")
+    }
+    val subBtn by style {
+        cursor("pointer")
+    }
+}
\ No newline at end of file
diff --git a/mosogepsch/src/jsMain/resources/index.html b/mosogepsch/src/jsMain/resources/index.html
index 250a1dd96f189bd2da8442802fdba07f549052ea..6b990b5ed911b0632e44076e9aaff3e2614dc99f 100644
--- a/mosogepsch/src/jsMain/resources/index.html
+++ b/mosogepsch/src/jsMain/resources/index.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="UTF-8">
     <title>MosógépSCH</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
 </head>
 <body>