diff options
-rw-r--r-- | index.html | 71 | ||||
-rw-r--r-- | robots.txt | 2 | ||||
-rw-r--r-- | script.js | 69 | ||||
-rw-r--r-- | style.css | 224 |
4 files changed, 366 insertions, 0 deletions
diff --git a/index.html b/index.html new file mode 100644 index 0000000..01e48f8 --- /dev/null +++ b/index.html | |||
@@ -0,0 +1,71 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html lang="en"> | ||
3 | |||
4 | <head> | ||
5 | <meta charset="utf-8" /> | ||
6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
7 | <meta name="robots" content="noindex" /> | ||
8 | |||
9 | <title>Livestream IRC</title> | ||
10 | |||
11 | <link rel="stylesheet" href="style.css" /> | ||
12 | </head> | ||
13 | |||
14 | <body> | ||
15 | <section class="c-section c-section--create"> | ||
16 | <form class="c-form" action="index.html" method="GET"> | ||
17 | <label class="c-form-field"> | ||
18 | <div class="c-form-field__label">Title</div> | ||
19 | <input class="c-form-field__input" name="title" /> | ||
20 | <div class="c-form-field__help"> | ||
21 | Optional title for your stream page. | ||
22 | </div> | ||
23 | </label> | ||
24 | <label class="c-form-field"> | ||
25 | <div class="c-form-field__label">Stream embed URL</div> | ||
26 | <input class="c-form-field__input" name="stream" /> | ||
27 | <div class="c-form-field__help"> | ||
28 | The URL of the stream you want to embed. | ||
29 | Make sure you use an embed URL so only the player is displayed instead of the whole website. | ||
30 | In case of PeerTube, replace the "/watch/" part in the URL with "/embed/". | ||
31 | </div> | ||
32 | </label> | ||
33 | <label class="c-form-field"> | ||
34 | <div class="c-form-field__label">IRC channel URL</div> | ||
35 | <input class="c-form-field__input" name="irc" /> | ||
36 | <div class="c-form-field__help"> | ||
37 | The URL pointing to a channel on an IRC server. | ||
38 | It has the format "ircs://chat.server/channel", where "chat.server" is the IRC server | ||
39 | and "channel" the name of the channel without the leading #. | ||
40 | Use "irc://..." if the server does not support TLS. | ||
41 | </div> | ||
42 | </label> | ||
43 | <button class="c-form-field">Create</button> | ||
44 | </form> | ||
45 | </section> | ||
46 | |||
47 | <section class="c-section c-section--main u-hidden"> | ||
48 | <div class="c-msg"> | ||
49 | <p class="c-msg__content"> | ||
50 | <a class="c-irc-link" href="ircs://irc.vulpes.one:6697/livestream">Join #livestream on irc.vulpes.one</a> | ||
51 | </p> | ||
52 | <button class="c-msg__close"> | ||
53 | <div class="c-close-icon"></div> | ||
54 | </button> | ||
55 | </div> | ||
56 | |||
57 | <div class="c-frame c-frame--pt"> | ||
58 | <iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" frameborder="0" | ||
59 | allowfullscreen></iframe> | ||
60 | </div> | ||
61 | |||
62 | <div class="c-frame c-frame--irc"> | ||
63 | <iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups allow-forms" | ||
64 | frameborder="0"></iframe> | ||
65 | </div> | ||
66 | </section> | ||
67 | |||
68 | <script type=text/javascript src="script.js"></script> | ||
69 | </body> | ||
70 | |||
71 | </html> | ||
diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/robots.txt | |||
@@ -0,0 +1,2 @@ | |||
1 | User-agent: * | ||
2 | Disallow: / | ||
diff --git a/script.js b/script.js new file mode 100644 index 0000000..e2c8f36 --- /dev/null +++ b/script.js | |||
@@ -0,0 +1,69 @@ | |||
1 | (() => { | ||
2 | const queryString = window.location.search; | ||
3 | const urlParams = new URLSearchParams(queryString); | ||
4 | |||
5 | const title = urlParams.get("title"); | ||
6 | const streamUrl = urlParams.get("stream"); | ||
7 | const ircUrl = urlParams.get("irc"); | ||
8 | |||
9 | if (streamUrl === null || ircUrl === null) { | ||
10 | return; | ||
11 | } | ||
12 | |||
13 | /** @type HTMLInputElement */ | ||
14 | const titleEl = document.querySelector(".c-form-field__input[name=title]"); | ||
15 | /** @type HTMLInputElement */ | ||
16 | const streamEl = document.querySelector(".c-form-field__input[name=stream]"); | ||
17 | /** @type HTMLInputElement */ | ||
18 | const ircEl = document.querySelector(".c-form-field__input[name=irc]"); | ||
19 | |||
20 | titleEl.value = title; | ||
21 | streamEl.value = streamUrl; | ||
22 | ircEl.value = ircUrl; | ||
23 | |||
24 | if (streamUrl.trim().length === 0 || ircUrl.trim().length === 0) { | ||
25 | return; | ||
26 | } | ||
27 | |||
28 | const normalizedircUrl = ircUrl.startsWith("irc://") | ||
29 | ? ircUrl.substring(6) | ||
30 | : ircUrl.startsWith("ircs://") | ||
31 | ? ircUrl.substring(7) | ||
32 | : ircUrl; | ||
33 | const ircUrlComponents = normalizedircUrl.split("/"); | ||
34 | |||
35 | if (ircUrlComponents.length !== 2) { | ||
36 | return; | ||
37 | } | ||
38 | |||
39 | if (title !== null && title.trim().length !== 0) { | ||
40 | document.querySelector("title").textContent = title; | ||
41 | } | ||
42 | |||
43 | const ircChan = ircUrlComponents[0]; | ||
44 | const ircHost = ircUrlComponents[1]; | ||
45 | |||
46 | document.querySelector(".c-section--create").classList.add("u-hidden"); | ||
47 | document.querySelector(".c-section--main").classList.remove("u-hidden"); | ||
48 | |||
49 | /** @type HTMLIFrameElement */ | ||
50 | const streamFrameEl = document.querySelector(".c-frame--pt iframe"); | ||
51 | /** @type HTMLIFrameElement */ | ||
52 | const ircFrameEl = document.querySelector(".c-frame--irc iframe"); | ||
53 | |||
54 | streamFrameEl.src = streamUrl; | ||
55 | ircFrameEl.src = "https://irc.vulpes.one/?uri=" + ircUrl; | ||
56 | |||
57 | /** @type HTMLAnchorElement */ | ||
58 | const ircLink = document.querySelector(".c-irc-link"); | ||
59 | ircLink.href = ircUrl; | ||
60 | ircLink.textContent = `Join #${ircChan} on ${ircHost}`; | ||
61 | |||
62 | document.querySelector(".c-msg__close").addEventListener("click", (e) => { | ||
63 | e.preventDefault(); | ||
64 | |||
65 | /** @type HTMLElement */ | ||
66 | const el = e.currentTarget; | ||
67 | el.parentElement.remove(); | ||
68 | }); | ||
69 | })(); | ||
diff --git a/style.css b/style.css new file mode 100644 index 0000000..6be7ed3 --- /dev/null +++ b/style.css | |||
@@ -0,0 +1,224 @@ | |||
1 | |||
2 | |||
3 | :root { | ||
4 | --gray0: hsl(270, 0%, 7%); | ||
5 | --gray1: hsl(270, 0%, 10%); | ||
6 | --gray2: hsl(270, 1%, 16%); | ||
7 | --gray3: hsl(270, 1%, 24%); | ||
8 | --gray4: hsl(270, 1%, 35%); | ||
9 | --gray5: hsl(270, 2%, 54%); | ||
10 | --gray6: hsl(270, 2%, 73%); | ||
11 | --gray7: hsl(270, 2%, 100%); | ||
12 | |||
13 | /* * */ | ||
14 | |||
15 | --bg-hi: var(--gray0); | ||
16 | --bg: var(--gray1); | ||
17 | |||
18 | --obj-hi: var(--gray2); | ||
19 | --obj: var(--gray3); | ||
20 | --obj-lo: var(--gray4); | ||
21 | |||
22 | --fg-hi: var(--gray5); | ||
23 | --fg: var(--gray6); | ||
24 | --fg-lo: var(--gray7); | ||
25 | |||
26 | /* * */ | ||
27 | |||
28 | --color--yellow: hsl(38, 100%, 76%); | ||
29 | } | ||
30 | |||
31 | html { | ||
32 | height: 100%; | ||
33 | } | ||
34 | |||
35 | body { | ||
36 | height: 100%; | ||
37 | margin: 0; | ||
38 | font-family: IBM Plex Sans, -apple-system, system-ui, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, Helvetica Neue, Arial; | ||
39 | line-height: 1.5; | ||
40 | background-color: var(--bg); | ||
41 | color: var(--fg); | ||
42 | } | ||
43 | |||
44 | a { | ||
45 | text-decoration: none; | ||
46 | } | ||
47 | |||
48 | p { | ||
49 | margin: 0; | ||
50 | } | ||
51 | |||
52 | iframe { | ||
53 | display: block; | ||
54 | width: 100%; | ||
55 | height: 100%; | ||
56 | } | ||
57 | |||
58 | input { | ||
59 | padding: .5em .7em; | ||
60 | background: var(--obj); | ||
61 | color: var(--fg-lo); | ||
62 | border: 2px solid transparent; | ||
63 | border-radius: 3px; | ||
64 | transition: border-color .2s; | ||
65 | } | ||
66 | |||
67 | input:hover, | ||
68 | input:focus { | ||
69 | border-color: var(--color--yellow); | ||
70 | box-shadow: none; | ||
71 | outline: 0; | ||
72 | } | ||
73 | |||
74 | button { | ||
75 | padding: .6em 1.3em; | ||
76 | font: inherit; | ||
77 | font-size: 12px; | ||
78 | font-weight: bold; | ||
79 | text-transform: uppercase; | ||
80 | letter-spacing: .2em; | ||
81 | background: transparent; | ||
82 | color: var(--color--yellow); | ||
83 | border: 2px solid var(--color--yellow); | ||
84 | border-radius: 3px; | ||
85 | transition: background-color .2s, color .2s; | ||
86 | } | ||
87 | |||
88 | button:hover, | ||
89 | button:focus { | ||
90 | background-color: var(--color--yellow); | ||
91 | color: #000; | ||
92 | } | ||
93 | |||
94 | :link, | ||
95 | :visited { | ||
96 | color: #850000; | ||
97 | } | ||
98 | |||
99 | .c-section { | ||
100 | height: 100%; | ||
101 | } | ||
102 | |||
103 | .c-section--main { | ||
104 | display: grid; | ||
105 | grid-template-areas: "video msg" "video chat"; | ||
106 | grid-template-rows: auto 1fr; | ||
107 | grid-template-columns: 1fr 22em; | ||
108 | } | ||
109 | |||
110 | .c-form { | ||
111 | display: block; | ||
112 | padding: 2rem; | ||
113 | margin: 0 auto; | ||
114 | max-width: 600px; | ||
115 | } | ||
116 | |||
117 | .c-form-msg:empty { | ||
118 | display: none; | ||
119 | } | ||
120 | |||
121 | .c-form-field { | ||
122 | display: block; | ||
123 | margin-top: 2rem; | ||
124 | } | ||
125 | |||
126 | .c-form-field__label { | ||
127 | font-size: 15px; | ||
128 | font-weight: bold; | ||
129 | color: var(--fg-lo); | ||
130 | } | ||
131 | |||
132 | .c-form-field__help { | ||
133 | display: block; | ||
134 | margin-top: .5rem; | ||
135 | color: var(--fg-hi); | ||
136 | font-size: 15px; | ||
137 | } | ||
138 | |||
139 | .c-form-field__input { | ||
140 | box-sizing: border-box; | ||
141 | display: block; | ||
142 | width: 100%; | ||
143 | margin-top: .5rem; | ||
144 | } | ||
145 | |||
146 | .c-frame--pt { | ||
147 | grid-area: video; | ||
148 | } | ||
149 | |||
150 | .c-frame--irc { | ||
151 | grid-area: chat; | ||
152 | } | ||
153 | |||
154 | .c-msg { | ||
155 | grid-area: msg; | ||
156 | display: flex; | ||
157 | color: #850000; | ||
158 | background-color: #fcc; | ||
159 | font-size: 14px; | ||
160 | border: 1px solid #ffb8b8; | ||
161 | } | ||
162 | |||
163 | .c-msg__content { | ||
164 | display: block; | ||
165 | width: 100%; | ||
166 | padding: .75rem 1.25rem; | ||
167 | } | ||
168 | |||
169 | .c-close-icon { | ||
170 | width: 1.1em; | ||
171 | height: 2px; | ||
172 | position: relative; | ||
173 | } | ||
174 | |||
175 | .c-close-icon::before, | ||
176 | .c-close-icon::after { | ||
177 | position: absolute; | ||
178 | left: 0; | ||
179 | display: block; | ||
180 | height: 2px; | ||
181 | content: ''; | ||
182 | background-color: currentColor; | ||
183 | transform-origin: 50% 50%; | ||
184 | } | ||
185 | |||
186 | .c-close-icon::before { | ||
187 | top: 0; | ||
188 | width: 100%; | ||
189 | transform: rotate(45deg); | ||
190 | transition: background-color .3s ease; | ||
191 | } | ||
192 | |||
193 | .c-close-icon::after { | ||
194 | bottom: 0; | ||
195 | transform: rotate(-45deg); | ||
196 | width: 100%; | ||
197 | transition: background-color .3s ease; | ||
198 | } | ||
199 | |||
200 | .c-msg__close { | ||
201 | display: block; | ||
202 | padding: .75rem 1.25rem; | ||
203 | margin-left: auto; | ||
204 | background: transparent; | ||
205 | color: currentColor; | ||
206 | border: 0; | ||
207 | } | ||
208 | |||
209 | .c-msg__close:hover { | ||
210 | background: transparent; | ||
211 | color: currentColor; | ||
212 | } | ||
213 | |||
214 | .u-hidden { | ||
215 | display: none; | ||
216 | } | ||
217 | |||
218 | @media (max-width: 1024px) { | ||
219 | .c-section--main { | ||
220 | grid-template-areas: "msg" "video" "chat"; | ||
221 | grid-template-rows: auto 1fr 22em; | ||
222 | grid-template-columns: 1fr; | ||
223 | } | ||
224 | } | ||