add options pane, add ping volume and hide player controls settings #8
					 6 changed files with 644 additions and 489 deletions
				
			
		|  | @ -53,17 +53,52 @@ | |||
| 
 | ||||
|     <div id="video-container"></div> | ||||
|     <div id="chatbox-container"> | ||||
|       <div id="viewer-list"></div> | ||||
|       <div id="chatbox"></div> | ||||
|       <form id="chatbox-send"> | ||||
|         <input | ||||
|           type="text" | ||||
|           placeholder="Message... (/help for commands)" | ||||
|           list="emoji-autocomplete" | ||||
|         /> | ||||
|         <div id="emoji-autocomplete"></div> | ||||
|         <!-- DO NOT ADD SPACING INSIDE THE TAG IT WILL BREAK THE CSS kthxbye --> | ||||
|       </form> | ||||
|       <section id="viewing"> | ||||
|         <div id="viewer-list"></div> | ||||
|         <div id="chatbox"></div> | ||||
|         <form id="chatbox-send"> | ||||
|           <input | ||||
|             type="text" | ||||
|             placeholder="Message... (/help for commands)" | ||||
|             list="emoji-autocomplete" | ||||
|           /> | ||||
|           <div id="emoji-autocomplete"></div> | ||||
|           <!-- DO NOT ADD SPACING INSIDE THE TAG IT WILL BREAK THE CSS kthxbye --> | ||||
|         </form> | ||||
|       </section> | ||||
|       <section id="options"> | ||||
|         <h2>settings</h2> | ||||
|         <hr /> | ||||
|         <form id="options-form"> | ||||
|           <label for="plingVolume" | ||||
|             ><input | ||||
|               type="range" | ||||
|               min="0" | ||||
|               max="100" | ||||
|               value="100" | ||||
|               id="plingVolume" | ||||
|               onchange="handlePlingVolume(this)" | ||||
|             /> | ||||
|             ping volume</label | ||||
|           > | ||||
|           <label | ||||
|           ><input | ||||
|             id="playerControlsShown" | ||||
|             type="checkbox" | ||||
|             onchange="togglePlayerControlsShown(this)" | ||||
|           />hide controls when loading video player</label | ||||
|         > | ||||
|         </form> | ||||
|       </section> | ||||
|       <div id="options-toggle"> | ||||
|         <button | ||||
|           aria-label="settings" | ||||
|           id="options-icon" | ||||
|           onclick="toggleOptionPane(event, this)" | ||||
|         > | ||||
|           ⚙️ | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <script type="module" src="/main.mjs?v=bfdcf2"></script> | ||||
|  |  | |||
							
								
								
									
										43
									
								
								frontend/lib/options-pane.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								frontend/lib/options-pane.mjs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| export const toggleOptionPane = (event, element) => { | ||||
|   event.preventDefault(); | ||||
|   // show options
 | ||||
|   if ( | ||||
|     !document.querySelector("#options").style.display || | ||||
|     document.querySelector("#options").style.display === "none" | ||||
|   ) { | ||||
|     // using this to do any potential init logic for the fields too
 | ||||
|     loadPlayerControlsShown(document.querySelector("#playerControlsShown")) | ||||
|     loadPlingVolume(document.querySelector("#plingVolume")) | ||||
| 
 | ||||
|     element.innerText = "❌"; | ||||
|     document.querySelector("#options").style.display = "block"; | ||||
|     return (document.querySelector("#viewing").style.display = "none"); | ||||
|   } | ||||
|   // hide options
 | ||||
|   element.innerText = "⚙️"; | ||||
|   document.querySelector("#options").style.display = "none"; | ||||
|   document.querySelector("#viewing").style.display = "block"; | ||||
| }; | ||||
| 
 | ||||
| const getPlayerControlsShown = () =>  localStorage.getItem("watch-party-default-allow-controls") || false | ||||
| // delete from storage on false to prevent weird js boolean parsing (Boolean('false') === True)
 | ||||
| const setPlayerControlShown = (boolean) => !boolean  | ||||
| ? localStorage.removeItem("watch-party-default-allow-controls") | ||||
| : localStorage.setItem("watch-party-default-allow-controls", boolean) | ||||
| export const togglePlayerControlsShown = (element) => { | ||||
|   const isShown = element.checked | ||||
|   setPlayerControlShown(!isShown) | ||||
| } | ||||
| const loadPlayerControlsShown = (element) => { | ||||
|   const isShown = getPlayerControlsShown() | ||||
|   element.checked = !isShown | ||||
| } | ||||
| 
 | ||||
| const getPlingVolume = () =>  localStorage.getItem("watch-party-pling-volume") || 100 | ||||
| const setPlingVolume = (value) => localStorage.setItem("watch-party-pling-volume", value) | ||||
| export const handlePlingVolume = (element) => { | ||||
|   setPlingVolume(element.value) | ||||
| } | ||||
| const loadPlingVolume = (element) => { | ||||
|   element.value = getPlingVolume() | ||||
| } | ||||
|  | @ -1,80 +1,82 @@ | |||
| export const pling = () => { | ||||
|   const maxGain = 0.3; | ||||
|   const duration = 0.22; | ||||
|   const fadeDuration = 0.1; | ||||
|   const secondBeepOffset = 0.05; | ||||
|   const thirdBeepOffset = 2 * secondBeepOffset; | ||||
| 
 | ||||
|   const ctx = new AudioContext(); | ||||
| 
 | ||||
|   const firstBeepGain = ctx.createGain(); | ||||
|   firstBeepGain.connect(ctx.destination); | ||||
|   firstBeepGain.gain.setValueAtTime(0.01, ctx.currentTime); | ||||
|   firstBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + fadeDuration | ||||
|   ); | ||||
|   firstBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + (duration - fadeDuration) | ||||
|   ); | ||||
|   firstBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + duration | ||||
|   ); | ||||
| 
 | ||||
|   const firstBeep = ctx.createOscillator(); | ||||
|   firstBeep.connect(firstBeepGain); | ||||
|   firstBeep.frequency.value = 400; | ||||
|   firstBeep.type = "sine"; | ||||
| 
 | ||||
|   const secondBeepGain = ctx.createGain(); | ||||
|   secondBeepGain.connect(ctx.destination); | ||||
|   secondBeepGain.gain.setValueAtTime(0.01, ctx.currentTime + secondBeepOffset); | ||||
|   secondBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + secondBeepOffset + fadeDuration | ||||
|   ); | ||||
|   secondBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + secondBeepOffset + (duration - fadeDuration) | ||||
|   ); | ||||
|   secondBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + secondBeepOffset + duration | ||||
|   ); | ||||
| 
 | ||||
|   const secondBeep = ctx.createOscillator(); | ||||
|   secondBeep.connect(secondBeepGain); | ||||
|   secondBeep.frequency.value = 600; | ||||
|   secondBeep.type = "sine"; | ||||
| 
 | ||||
|   const thirdBeepGain = ctx.createGain(); | ||||
|   thirdBeepGain.connect(ctx.destination); | ||||
|   thirdBeepGain.gain.setValueAtTime(0.01, ctx.currentTime + thirdBeepOffset); | ||||
|   thirdBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + thirdBeepOffset + fadeDuration | ||||
|   ); | ||||
|   thirdBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + thirdBeepOffset + (duration - fadeDuration) | ||||
|   ); | ||||
|   thirdBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + thirdBeepOffset + duration | ||||
|   ); | ||||
| 
 | ||||
|   const thirdBeep = ctx.createOscillator(); | ||||
|   thirdBeep.connect(thirdBeepGain); | ||||
|   thirdBeep.frequency.value = 900; | ||||
|   thirdBeep.type = "sine"; | ||||
| 
 | ||||
|   firstBeep.start(ctx.currentTime); | ||||
|   firstBeep.stop(ctx.currentTime + duration); | ||||
|   secondBeep.start(ctx.currentTime + secondBeepOffset); | ||||
|   secondBeep.stop(ctx.currentTime + (secondBeepOffset + duration)); | ||||
|   thirdBeep.start(ctx.currentTime + thirdBeepOffset); | ||||
|   thirdBeep.stop(ctx.currentTime + (thirdBeepOffset + duration)); | ||||
| }; | ||||
| 
 | ||||
| export const pling = () => { | ||||
|   // technically volume 0 breaks it but its the wanted outcome i guess?
 | ||||
|   const maxGain = | ||||
|     (Number(localStorage.getItem("watch-party-pling-volume")) / 100 ?? 1) * 0.3; | ||||
| 
 | ||||
|   const duration = 0.22; | ||||
|   const fadeDuration = 0.1; | ||||
|   const secondBeepOffset = 0.05; | ||||
|   const thirdBeepOffset = 2 * secondBeepOffset; | ||||
| 
 | ||||
|   const ctx = new AudioContext(); | ||||
| 
 | ||||
|   const firstBeepGain = ctx.createGain(); | ||||
|   firstBeepGain.connect(ctx.destination); | ||||
|   firstBeepGain.gain.setValueAtTime(0.01, ctx.currentTime); | ||||
|   firstBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + fadeDuration | ||||
|   ); | ||||
|   firstBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + (duration - fadeDuration) | ||||
|   ); | ||||
|   firstBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + duration | ||||
|   ); | ||||
| 
 | ||||
|   const firstBeep = ctx.createOscillator(); | ||||
|   firstBeep.connect(firstBeepGain); | ||||
|   firstBeep.frequency.value = 400; | ||||
|   firstBeep.type = "sine"; | ||||
| 
 | ||||
|   const secondBeepGain = ctx.createGain(); | ||||
|   secondBeepGain.connect(ctx.destination); | ||||
|   secondBeepGain.gain.setValueAtTime(0.01, ctx.currentTime + secondBeepOffset); | ||||
|   secondBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + secondBeepOffset + fadeDuration | ||||
|   ); | ||||
|   secondBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + secondBeepOffset + (duration - fadeDuration) | ||||
|   ); | ||||
|   secondBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + secondBeepOffset + duration | ||||
|   ); | ||||
| 
 | ||||
|   const secondBeep = ctx.createOscillator(); | ||||
|   secondBeep.connect(secondBeepGain); | ||||
|   secondBeep.frequency.value = 600; | ||||
|   secondBeep.type = "sine"; | ||||
| 
 | ||||
|   const thirdBeepGain = ctx.createGain(); | ||||
|   thirdBeepGain.connect(ctx.destination); | ||||
|   thirdBeepGain.gain.setValueAtTime(0.01, ctx.currentTime + thirdBeepOffset); | ||||
|   thirdBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + thirdBeepOffset + fadeDuration | ||||
|   ); | ||||
|   thirdBeepGain.gain.setValueAtTime( | ||||
|     maxGain, | ||||
|     ctx.currentTime + thirdBeepOffset + (duration - fadeDuration) | ||||
|   ); | ||||
|   thirdBeepGain.gain.exponentialRampToValueAtTime( | ||||
|     0.01, | ||||
|     ctx.currentTime + thirdBeepOffset + duration | ||||
|   ); | ||||
| 
 | ||||
|   const thirdBeep = ctx.createOscillator(); | ||||
|   thirdBeep.connect(thirdBeepGain); | ||||
|   thirdBeep.frequency.value = 900; | ||||
|   thirdBeep.type = "sine"; | ||||
| 
 | ||||
|   firstBeep.start(ctx.currentTime); | ||||
|   firstBeep.stop(ctx.currentTime + duration); | ||||
|   secondBeep.start(ctx.currentTime + secondBeepOffset); | ||||
|   secondBeep.stop(ctx.currentTime + (secondBeepOffset + duration)); | ||||
|   thirdBeep.start(ctx.currentTime + thirdBeepOffset); | ||||
|   thirdBeep.stop(ctx.currentTime + (thirdBeepOffset + duration)); | ||||
| }; | ||||
|  |  | |||
|  | @ -228,7 +228,6 @@ export const joinSession = async () => { | |||
|       is_playing | ||||
|     ); | ||||
| 
 | ||||
|     // TODO: Allow the user to set this somewhere
 | ||||
|     let defaultAllowControls = false; | ||||
|     try { | ||||
|       defaultAllowControls = localStorage.getItem( | ||||
|  |  | |||
|  | @ -1,7 +1,15 @@ | |||
| import { setupJoinSessionForm } from "./lib/join-session.mjs?v=bfdcf2"; | ||||
| import { | ||||
|   toggleOptionPane, | ||||
|   togglePlayerControlsShown, | ||||
|   handlePlingVolume | ||||
| } from "./lib/options-pane.mjs?v=bfdcf2"; | ||||
| 
 | ||||
| const main = () => { | ||||
|   setupJoinSessionForm(); | ||||
|   window.toggleOptionPane = toggleOptionPane; | ||||
|   window.togglePlayerControlsShown = togglePlayerControlsShown; | ||||
|   window.handlePlingVolume = handlePlingVolume | ||||
| }; | ||||
| 
 | ||||
| if (document.readyState === "complete") { | ||||
|  |  | |||
|  | @ -1,397 +1,465 @@ | |||
| *, | ||||
| *:before, | ||||
| *:after { | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| :root { | ||||
|   --bg-rgb: 28, 23, 36; | ||||
|   --fg-rgb: 234, 234, 248; | ||||
|   --accent-rgb: 181, 127, 220; | ||||
|   --fg: rgb(var(--fg-rgb)); | ||||
|   --bg: rgb(var(--bg-rgb)); | ||||
|   --default-user-color: rgb(126, 208, 255); | ||||
|   --accent: rgb(var(--accent-rgb)); | ||||
|   --fg-transparent: rgba(var(--fg-rgb), 0.25); | ||||
|   --bg-transparent: rgba(var(--bg-rgb), 0.25); | ||||
|   --autocomplete-bg: linear-gradient( | ||||
|       var(--fg-transparent), | ||||
|       var(--fg-transparent) | ||||
|     ), | ||||
|     linear-gradient(var(--bg), var(--bg)); | ||||
|   --chip-bg: linear-gradient( | ||||
|       var(--accent-transparent), | ||||
|       var(--accent-transparent) | ||||
|     ), | ||||
|     linear-gradient(var(--bg), var(--bg)); | ||||
|   --accent-transparent: rgba(var(--accent-rgb), 0.25); | ||||
| } | ||||
| 
 | ||||
| html { | ||||
|   background-color: var(--bg); | ||||
|   color: var(--fg); | ||||
|   font-size: 1.125rem; | ||||
|   font-family: sans-serif; | ||||
| } | ||||
| 
 | ||||
| html, | ||||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   overflow: hidden; | ||||
|   overscroll-behavior: none; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| video { | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   object-fit: contain; | ||||
| } | ||||
| 
 | ||||
| #video-container { | ||||
|   flex-grow: 0; | ||||
|   flex-shrink: 1; | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   color: var(--accent); | ||||
| } | ||||
| 
 | ||||
| .chip { | ||||
|   color: var(--fg); | ||||
|   background: var(--chip-bg); | ||||
|   text-decoration: none; | ||||
|   padding: 0 0.5rem 0 1.45rem; | ||||
|   display: inline-flex; | ||||
|   position: relative; | ||||
|   font-size: 0.9rem; | ||||
|   height: 1.125rem; | ||||
|   align-items: center; | ||||
|   border-radius: 2rem; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .chip::before { | ||||
|   content: ""; | ||||
|   position: absolute; | ||||
|   left: 0; | ||||
|   top: 0; | ||||
|   width: 1.125rem; | ||||
|   height: 100%; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   text-align: center; | ||||
|   background: var(--accent-transparent); | ||||
|   background-repeat: no-repeat; | ||||
|   background-size: 18px; | ||||
|   background-position: center; | ||||
| } | ||||
| 
 | ||||
| .join-chip::before { | ||||
|   background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTggNXYxNGwxMS03eiIvPjwvc3ZnPg=="); | ||||
| } | ||||
| 
 | ||||
| .time-chip::before { | ||||
|   background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHoiLz48cGF0aCBkPSJNMTIuNSA3SDExdjZsNS4yNSAzLjE1Ljc1LTEuMjMtNC41LTIuNjd6Ii8+PC9zdmc+"); | ||||
| } | ||||
| 
 | ||||
| label { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| input[type="url"], | ||||
| input[type="text"] { | ||||
|   background: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid rgba(0, 0, 0, 0.12); | ||||
|   border-radius: 6px; | ||||
|   color: rgba(0, 0, 0, 0.8); | ||||
|   display: block; | ||||
| 
 | ||||
|   margin: 0.5em 0; | ||||
|   padding: 0.5em 1em; | ||||
|   line-height: 1.5; | ||||
| 
 | ||||
|   font-family: sans-serif; | ||||
|   font-size: 1em; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   resize: none; | ||||
|   overflow-x: wrap; | ||||
|   overflow-y: scroll; | ||||
| } | ||||
| 
 | ||||
| button { | ||||
|   background-color: var(--accent); | ||||
|   border: var(--accent); | ||||
|   border-radius: 6px; | ||||
|   color: #fff; | ||||
|   padding: 0.5em 1em; | ||||
|   display: inline-block; | ||||
|   font-weight: 400; | ||||
|   text-align: center; | ||||
|   white-space: nowrap; | ||||
|   vertical-align: middle; | ||||
| 
 | ||||
|   font-family: sans-serif; | ||||
|   font-size: 1em; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   user-select: none; | ||||
|   border: 1px solid rgba(0, 0, 0, 0); | ||||
|   line-height: 1.5; | ||||
|   cursor: pointer; | ||||
|   margin: 0.5em 0; | ||||
| } | ||||
| 
 | ||||
| button:disabled { | ||||
|   filter: saturate(0.75); | ||||
|   opacity: 0.75; | ||||
|   cursor: default; | ||||
| } | ||||
| 
 | ||||
| button.small-button { | ||||
|   font-size: 0.75em; | ||||
|   padding-top: 0; | ||||
|   padding-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .subtitle-track-group { | ||||
|   display: flex; | ||||
| } | ||||
| 
 | ||||
| .subtitle-track-group > * { | ||||
|   margin-top: 0 !important; | ||||
|   margin-bottom: 0 !important; | ||||
|   margin-right: 1ch !important; | ||||
| } | ||||
| 
 | ||||
| #pre-join-controls, | ||||
| #create-controls { | ||||
|   margin: 0; | ||||
|   flex-grow: 1; | ||||
|   overflow-y: auto; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
| 
 | ||||
| #join-session-form, | ||||
| #create-session-form { | ||||
|   width: 500px; | ||||
|   max-width: 100%; | ||||
|   padding: 1rem; | ||||
| } | ||||
| 
 | ||||
| #join-session-form > *:first-child, | ||||
| #create-session-form > *:first-child { | ||||
|   margin-top: 0; | ||||
| } | ||||
| 
 | ||||
| #post-create-message { | ||||
|   display: none; | ||||
|   width: 100%; | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| #chatbox-container { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .chat-message { | ||||
|   overflow-wrap: break-word; | ||||
|   margin-bottom: 0.125rem; | ||||
| } | ||||
| 
 | ||||
| .chat-message > strong, | ||||
| #viewer-list strong { | ||||
|   color: var(--user-color, var(--default-user-color)); | ||||
| } | ||||
| 
 | ||||
| .chat-message.user-join, | ||||
| .chat-message.user-leave, | ||||
| .chat-message.ping { | ||||
|   font-style: italic; | ||||
| } | ||||
| 
 | ||||
| .chat-message.set-time, | ||||
| .chat-message.set-playing, | ||||
| .chat-message.join-session { | ||||
|   font-style: italic; | ||||
|   text-align: right; | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| .chat-message.command-message { | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| .chat-message.set-time > strong, | ||||
| .chat-message.set-playing > strong, | ||||
| .chat-message.join-session > strong { | ||||
|   color: unset !important; | ||||
| } | ||||
| 
 | ||||
| .emoji { | ||||
|   width: 2ch; | ||||
|   height: 2ch; | ||||
|   object-fit: contain; | ||||
|   margin-bottom: -0.35ch; | ||||
| } | ||||
| 
 | ||||
| #chatbox { | ||||
|   padding: 0.5em 1em; | ||||
|   overflow-y: scroll; | ||||
|   flex-shrink: 1; | ||||
|   flex-grow: 1; | ||||
| } | ||||
| 
 | ||||
| #viewer-list { | ||||
|   padding: 0.5em 1em; | ||||
|   /* TODO: turn this into max-height instead of fixed height without breaking the chatbox height */ | ||||
|   overflow-y: scroll; | ||||
|   border-bottom: var(--fg-transparent); | ||||
|   border-bottom-style: solid; | ||||
|   max-height: 4rem; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| 
 | ||||
| #chatbox-container { | ||||
|   background-color: var(--bg); | ||||
|   flex-direction: column; | ||||
|   flex-grow: 1; | ||||
|   flex-shrink: 1; | ||||
|   flex-basis: 36ch; | ||||
|   min-width: 36ch; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| #chatbox-send { | ||||
|   padding: 0 1em; | ||||
|   padding-bottom: 0.5em; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| #chatbox-send > input { | ||||
|   font-size: 0.75em; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| #emoji-autocomplete { | ||||
|   position: absolute; | ||||
|   bottom: 3.25rem; | ||||
|   background-image: var(--autocomplete-bg); | ||||
|   border-radius: 6px; | ||||
|   width: calc(100% - 2rem); | ||||
|   max-height: 8.5rem; | ||||
|   overflow-y: auto; | ||||
|   clip-path: inset(0 0 0 0 round 8px); | ||||
| } | ||||
| 
 | ||||
| #emoji-autocomplete:empty { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .emoji-option { | ||||
|   background: transparent; | ||||
|   font-size: 0.75rem; | ||||
|   text-align: left; | ||||
|   margin: 0 0.25rem; | ||||
|   border-radius: 4px; | ||||
|   width: calc(100% - 0.5rem); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 0.25rem 0.5rem; | ||||
|   scroll-margin: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option:first-child { | ||||
|   margin-top: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option:last-child { | ||||
|   margin-bottom: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option .emoji { | ||||
|   width: 1.25rem; | ||||
|   height: 1.25rem; | ||||
|   margin: 0 0.5rem 0 0; | ||||
|   font-size: 2.25ch; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   overflow: hidden; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| 
 | ||||
| .emoji-name { | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| .emoji-option.selected { | ||||
|   background: var(--fg-transparent); | ||||
| } | ||||
| 
 | ||||
| #join-session-colour { | ||||
|   -moz-appearance: none; | ||||
|   -webkit-appearance: none; | ||||
|   appearance: none; | ||||
|   border: none; | ||||
|   padding: 0; | ||||
|   border-radius: 6px; | ||||
|   overflow: hidden; | ||||
|   margin: 0.5em 0; | ||||
|   height: 2rem; | ||||
|   width: 2.5rem; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-moz-color-swatch { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-webkit-color-swatch { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-webkit-color-swatch-wrapper { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| @media (min-aspect-ratio: 4/3) { | ||||
|   body { | ||||
|     flex-direction: row; | ||||
|   } | ||||
| 
 | ||||
|   #chatbox-container { | ||||
|     height: 100vh !important; | ||||
|     flex-grow: 0; | ||||
|   } | ||||
| 
 | ||||
|   #video-container { | ||||
|     flex-grow: 1; | ||||
|   } | ||||
| 
 | ||||
|   #chatbox { | ||||
|     height: calc(100vh - 5em - 4em) !important; | ||||
|   } | ||||
| } | ||||
| *, | ||||
| *:before, | ||||
| *:after { | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| :root { | ||||
|   --bg-rgb: 28, 23, 36; | ||||
|   --fg-rgb: 234, 234, 248; | ||||
|   --accent-rgb: 181, 127, 220; | ||||
|   --accent-darker: rgb(95, 37, 136); /*50% darker*/ | ||||
|   --accent-darkest: rgb(47, 19, 68); /*75% darker*/ | ||||
|   --fg: rgb(var(--fg-rgb)); | ||||
|   --bg: rgb(var(--bg-rgb)); | ||||
|   --default-user-color: rgb(126, 208, 255); | ||||
|   --accent: rgb(var(--accent-rgb)); | ||||
|   --fg-transparent: rgba(var(--fg-rgb), 0.25); | ||||
|   --bg-transparent: rgba(var(--bg-rgb), 0.25); | ||||
|   --autocomplete-bg: linear-gradient( | ||||
|       var(--fg-transparent), | ||||
|       var(--fg-transparent) | ||||
|     ), | ||||
|     linear-gradient(var(--bg), var(--bg)); | ||||
|   --chip-bg: linear-gradient( | ||||
|       var(--accent-transparent), | ||||
|       var(--accent-transparent) | ||||
|     ), | ||||
|     linear-gradient(var(--bg), var(--bg)); | ||||
|   --accent-transparent: rgba(var(--accent-rgb), 0.25); | ||||
| } | ||||
| 
 | ||||
| html { | ||||
|   background-color: var(--bg); | ||||
|   color: var(--fg); | ||||
|   font-size: 1.125rem; | ||||
|   font-family: sans-serif; | ||||
| } | ||||
| 
 | ||||
| html, | ||||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   overflow: hidden; | ||||
|   overscroll-behavior: none; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| video { | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   object-fit: contain; | ||||
| } | ||||
| 
 | ||||
| #video-container { | ||||
|   flex-grow: 0; | ||||
|   flex-shrink: 1; | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   color: var(--accent); | ||||
| } | ||||
| 
 | ||||
| .chip { | ||||
|   color: var(--fg); | ||||
|   background: var(--chip-bg); | ||||
|   text-decoration: none; | ||||
|   padding: 0 0.5rem 0 1.45rem; | ||||
|   display: inline-flex; | ||||
|   position: relative; | ||||
|   font-size: 0.9rem; | ||||
|   height: 1.125rem; | ||||
|   align-items: center; | ||||
|   border-radius: 2rem; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .chip::before { | ||||
|   content: ""; | ||||
|   position: absolute; | ||||
|   left: 0; | ||||
|   top: 0; | ||||
|   width: 1.125rem; | ||||
|   height: 100%; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   text-align: center; | ||||
|   background: var(--accent-transparent); | ||||
|   background-repeat: no-repeat; | ||||
|   background-size: 18px; | ||||
|   background-position: center; | ||||
| } | ||||
| 
 | ||||
| .join-chip::before { | ||||
|   background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTggNXYxNGwxMS03eiIvPjwvc3ZnPg=="); | ||||
| } | ||||
| 
 | ||||
| .time-chip::before { | ||||
|   background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyczQuNDcgMTAgOS45OSAxMEMxNy41MiAyMiAyMiAxNy41MiAyMiAxMlMxNy41MiAyIDExLjk5IDJ6TTEyIDIwYy00LjQyIDAtOC0zLjU4LTgtOHMzLjU4LTggOC04IDggMy41OCA4IDgtMy41OCA4LTggOHoiLz48cGF0aCBkPSJNMTIuNSA3SDExdjZsNS4yNSAzLjE1Ljc1LTEuMjMtNC41LTIuNjd6Ii8+PC9zdmc+"); | ||||
| } | ||||
| 
 | ||||
| label { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| input[type="url"], | ||||
| input[type="text"] { | ||||
|   background: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid rgba(0, 0, 0, 0.12); | ||||
|   border-radius: 6px; | ||||
|   color: rgba(0, 0, 0, 0.8); | ||||
|   display: block; | ||||
| 
 | ||||
|   margin: 0.5em 0; | ||||
|   padding: 0.5em 1em; | ||||
|   line-height: 1.5; | ||||
| 
 | ||||
|   font-family: sans-serif; | ||||
|   font-size: 1em; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   resize: none; | ||||
|   overflow-x: wrap; | ||||
|   overflow-y: scroll; | ||||
| } | ||||
| 
 | ||||
| button { | ||||
|   background-color: var(--accent); | ||||
|   border: var(--accent); | ||||
|   border-radius: 6px; | ||||
|   color: #fff; | ||||
|   padding: 0.5em 1em; | ||||
|   display: inline-block; | ||||
|   font-weight: 400; | ||||
|   text-align: center; | ||||
|   white-space: nowrap; | ||||
|   vertical-align: middle; | ||||
| 
 | ||||
|   font-family: sans-serif; | ||||
|   font-size: 1em; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   user-select: none; | ||||
|   border: 1px solid rgba(0, 0, 0, 0); | ||||
|   line-height: 1.5; | ||||
|   cursor: pointer; | ||||
|   margin: 0.5em 0; | ||||
| } | ||||
| 
 | ||||
| button:disabled { | ||||
|   filter: saturate(0.75); | ||||
|   opacity: 0.75; | ||||
|   cursor: default; | ||||
| } | ||||
| 
 | ||||
| button.small-button { | ||||
|   font-size: 0.75em; | ||||
|   padding-top: 0; | ||||
|   padding-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .subtitle-track-group { | ||||
|   display: flex; | ||||
| } | ||||
| 
 | ||||
| .subtitle-track-group > * { | ||||
|   margin-top: 0 !important; | ||||
|   margin-bottom: 0 !important; | ||||
|   margin-right: 1ch !important; | ||||
| } | ||||
| 
 | ||||
| #pre-join-controls, | ||||
| #create-controls { | ||||
|   margin: 0; | ||||
|   flex-grow: 1; | ||||
|   overflow-y: auto; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
| 
 | ||||
| #join-session-form, | ||||
| #create-session-form { | ||||
|   width: 500px; | ||||
|   max-width: 100%; | ||||
|   padding: 1rem; | ||||
| } | ||||
| 
 | ||||
| #join-session-form > *:first-child, | ||||
| #create-session-form > *:first-child { | ||||
|   margin-top: 0; | ||||
| } | ||||
| 
 | ||||
| #post-create-message { | ||||
|   display: none; | ||||
|   width: 100%; | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| #chatbox-container { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .chat-message { | ||||
|   overflow-wrap: break-word; | ||||
|   margin-bottom: 0.125rem; | ||||
| } | ||||
| 
 | ||||
| .chat-message > strong, | ||||
| #viewer-list strong { | ||||
|   color: var(--user-color, var(--default-user-color)); | ||||
| } | ||||
| 
 | ||||
| .chat-message.user-join, | ||||
| .chat-message.user-leave, | ||||
| .chat-message.ping { | ||||
|   font-style: italic; | ||||
| } | ||||
| 
 | ||||
| .chat-message.set-time, | ||||
| .chat-message.set-playing, | ||||
| .chat-message.join-session { | ||||
|   font-style: italic; | ||||
|   text-align: right; | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| .chat-message.command-message { | ||||
|   font-size: 0.85em; | ||||
| } | ||||
| 
 | ||||
| .chat-message.set-time > strong, | ||||
| .chat-message.set-playing > strong, | ||||
| .chat-message.join-session > strong { | ||||
|   color: unset !important; | ||||
| } | ||||
| 
 | ||||
| .emoji { | ||||
|   width: 2ch; | ||||
|   height: 2ch; | ||||
|   object-fit: contain; | ||||
|   margin-bottom: -0.35ch; | ||||
| } | ||||
| 
 | ||||
| #chatbox { | ||||
|   padding: 0.5em 1em; | ||||
|   overflow-y: scroll; | ||||
|   flex-shrink: 1; | ||||
|   flex-grow: 1; | ||||
| } | ||||
| 
 | ||||
| #viewer-list { | ||||
|   padding: 0.5em 1em; | ||||
|   /* TODO: turn this into max-height instead of fixed height without breaking the chatbox height */ | ||||
|   overflow-y: scroll; | ||||
|   border-bottom: var(--fg-transparent); | ||||
|   border-bottom-style: solid; | ||||
|   max-height: 4rem; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| 
 | ||||
| #chatbox-container { | ||||
|   background-color: var(--bg); | ||||
|   flex-direction: column; | ||||
|   flex-grow: 1; | ||||
|   flex-shrink: 1; | ||||
|   flex-basis: 36ch; | ||||
|   min-width: 36ch; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| #chatbox-send { | ||||
|   padding: 0 1em; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| #chatbox-send > input { | ||||
|   font-size: 0.75em; | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| #emoji-autocomplete { | ||||
|   position: absolute; | ||||
|   bottom: 3.25rem; | ||||
|   background-image: var(--autocomplete-bg); | ||||
|   border-radius: 6px; | ||||
|   width: calc(100% - 2rem); | ||||
|   max-height: 8.5rem; | ||||
|   overflow-y: auto; | ||||
|   clip-path: inset(0 0 0 0 round 8px); | ||||
| } | ||||
| 
 | ||||
| #emoji-autocomplete:empty { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .emoji-option { | ||||
|   background: transparent; | ||||
|   font-size: 0.75rem; | ||||
|   text-align: left; | ||||
|   margin: 0 0.25rem; | ||||
|   border-radius: 4px; | ||||
|   width: calc(100% - 0.5rem); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 0.25rem 0.5rem; | ||||
|   scroll-margin: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option:first-child { | ||||
|   margin-top: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option:last-child { | ||||
|   margin-bottom: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .emoji-option .emoji { | ||||
|   width: 1.25rem; | ||||
|   height: 1.25rem; | ||||
|   margin: 0 0.5rem 0 0; | ||||
|   font-size: 2.25ch; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   overflow: hidden; | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| 
 | ||||
| .emoji-name { | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| .emoji-option.selected { | ||||
|   background: var(--fg-transparent); | ||||
| } | ||||
| 
 | ||||
| #join-session-colour { | ||||
|   -moz-appearance: none; | ||||
|   -webkit-appearance: none; | ||||
|   appearance: none; | ||||
|   border: none; | ||||
|   padding: 0; | ||||
|   border-radius: 6px; | ||||
|   overflow: hidden; | ||||
|   margin: 0.5em 0; | ||||
|   height: 2rem; | ||||
|   width: 2.5rem; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| #options-toggle { | ||||
|   padding: 0 1em 1em; | ||||
|   position: relative; | ||||
|   text-align: right; | ||||
|   margin-top: auto; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon { | ||||
|   padding: 3px 10px; | ||||
|   font-size: 1em; | ||||
|   max-width: 3em; | ||||
| 
 | ||||
|   color: transparent; | ||||
|   text-shadow: 0 0 0 white; | ||||
|   border: none; | ||||
|   box-shadow:0px 0px 0px 2px var(--accent-darkest) inset; | ||||
|    | ||||
|   transform-style: preserve-3d; | ||||
|   transition: cubic-bezier(0, 0, 0.58, 1), cubic-bezier(0, 0, 0.58, 1); | ||||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon::before { | ||||
|   content: ""; | ||||
| 
 | ||||
|   position: absolute; | ||||
|    | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
| 
 | ||||
|   background-color: var(--accent-darker); | ||||
|   border-radius: inherit; | ||||
|   border: none; | ||||
|   box-shadow:0px 0px 0px 2px var(--accent-darkest) inset; | ||||
| 
 | ||||
|   transform: translate3d(0, 0.5em, -1em); | ||||
|   transition: cubic-bezier(0, 0, 0.58, 1), cubic-bezier(0, 0, 0.58, 1); | ||||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:hover { | ||||
|   transform: translate(0, 0.15em); | ||||
|   background-color: rgb(173, 113, 216); /*5% darker accent*/ | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:hover::before { | ||||
|   transform: translate3d(0, 0.35em, -1em); | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:active { | ||||
|   transform: translate(0em, 0.5em); | ||||
|   background-color: rgb(165, 100, 213); /*10% darker accent*/ | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:active::before { | ||||
|   transform: translate3d(0, 0, -1em); | ||||
| } | ||||
| 
 | ||||
| #options { | ||||
|   display: none; /* default for sections is block */ | ||||
|   padding: 01em; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-moz-color-swatch { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-webkit-color-swatch { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-webkit-color-swatch-wrapper { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| @media (min-aspect-ratio: 4/3) { | ||||
|   body { | ||||
|     flex-direction: row; | ||||
|   } | ||||
| 
 | ||||
|   #chatbox-container { | ||||
|     height: 100vh !important; | ||||
|     flex-grow: 0; | ||||
|   } | ||||
| 
 | ||||
|   #video-container { | ||||
|     flex-grow: 1; | ||||
|   } | ||||
| 
 | ||||
|   #chatbox { | ||||
|     height: calc(100vh - 5em - 4em) !important; | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue