diff --git a/app/root.css b/app/root.css
index 74101ea..9f4cad3 100644
--- a/app/root.css
+++ b/app/root.css
@@ -5,4 +5,7 @@ html, body {
line-height: 0;
margin: 0;
width: 100%;
+ * {
+ line-height: 1;
+ }
}
diff --git a/app/routes/_main-menu.play.$.$/route.jsx b/app/routes/_main-menu.play.$.$/route.jsx
new file mode 100644
index 0000000..51ca82b
--- /dev/null
+++ b/app/routes/_main-menu.play.$.$/route.jsx
@@ -0,0 +1,66 @@
+import {useEffect, useState} from 'react';
+import {useOutletContext, useParams} from 'react-router-dom';
+
+import ClientContext from '@/context/client.js';
+import Ui from '@/react-components/ui.jsx';
+
+export default function PlaySpecific() {
+ const Client = useOutletContext();
+ const [client, setClient] = useState();
+ const [disconnected, setDisconnected] = useState(false);
+ const params = useParams();
+ const [, url] = params['*'].split('/');
+ useEffect(() => {
+ if (!Client) {
+ return;
+ }
+ const client = new Client();
+ async function connect() {
+ await client.connect(url);
+ setClient(client);
+ }
+ connect();
+ return () => {
+ client.disconnect();
+ };
+ }, [Client, url]);
+ useEffect(() => {
+ if (!client) {
+ return;
+ }
+ function onConnectionStatus(status) {
+ switch (status) {
+ case 'aborted': {
+ setDisconnected(true);
+ break;
+ }
+ case 'connected': {
+ setDisconnected(false);
+ break;
+ }
+ }
+ }
+ client.addPacketListener('ConnectionStatus', onConnectionStatus);
+ return () => {
+ client.removePacketListener('ConnectionStatus', onConnectionStatus);
+ };
+ }, [client]);
+ useEffect(() => {
+ if (!disconnected) {
+ return;
+ }
+ async function reconnect() {
+ await client.connect(url);
+ }
+ reconnect();
+ const handle = setInterval(reconnect, 1000);
+ return () => {
+ clearInterval(handle);
+ };
+ }, [client, disconnected, url]);
+ return (
+