Styling!
parent
01dcae094a
commit
64a0c4e0b5
@ -1,72 +1,29 @@
|
|||||||
.app {
|
.screen {
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
|
||||||
|
|
||||||
.playArea {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 100vh;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.output {
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 16px;
|
|
||||||
padding-bottom: 0;
|
|
||||||
min-height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
padding: 16px;
|
|
||||||
padding-top: 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -1px;
|
width: 600px;
|
||||||
font-weight: bold;
|
height: 400px;
|
||||||
|
background: black;
|
||||||
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input input {
|
.overlay {
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
text-indent: 16px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
left: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input::before {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 16px;
|
top: 0;
|
||||||
}
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
.infoArea {
|
opacity: .4;
|
||||||
width: 300px;
|
overflow: hidden;
|
||||||
overflow-x: hidden;
|
pointer-events: none;
|
||||||
overflow-y: scroll;
|
mix-blend-mode: screen;
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.command {
|
|
||||||
font-weight: bold;
|
|
||||||
position: relative;
|
|
||||||
text-indent: 16px;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.command::before {
|
.overlay img {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
left: -16px;
|
height: 100%;
|
||||||
}
|
object-fit: cover;
|
||||||
|
filter: brightness(2.5);
|
||||||
.command::before, .input::before {
|
|
||||||
content: '> ';
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
@ -0,0 +1,30 @@
|
|||||||
|
import React, {useLayoutEffect, useRef} from 'react'
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import styles from './Text.module.css'
|
||||||
|
|
||||||
|
export default function Text({messages, currentInput, currentScroll}) {
|
||||||
|
const outputRef = useRef()
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
outputRef.current.scrollTop = currentScroll
|
||||||
|
}, [currentScroll])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.reflectedArea}>
|
||||||
|
<div ref={outputRef} className={styles.output}>
|
||||||
|
{messages.map((message, i) => {
|
||||||
|
if(message.type === 'message')
|
||||||
|
return <ReactMarkdown key={i}>{message.message}</ReactMarkdown>
|
||||||
|
|
||||||
|
if(message.type === 'command')
|
||||||
|
return <p key={i} className={styles.command}>{message.command}</p>
|
||||||
|
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={styles.input}>
|
||||||
|
<input tabIndex="-1" value={currentInput}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import React, {useState, useEffect, useRef} from 'react'
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import styles from './Text.module.css'
|
||||||
|
import Reflection from './ReflectedText'
|
||||||
|
|
||||||
|
export default function Text({messages, handleCommand}) {
|
||||||
|
const inputRef = useRef()
|
||||||
|
const outputRef = useRef()
|
||||||
|
const textRef = useRef()
|
||||||
|
|
||||||
|
const [currentInput, setCurrentInput] = useState('')
|
||||||
|
const [currentScroll, setCurrentScroll] = useState(0)
|
||||||
|
|
||||||
|
function onSubmit(ev) {
|
||||||
|
if(ev) ev.preventDefault()
|
||||||
|
|
||||||
|
if(!inputRef.current?.value.trim())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(inputRef.current){
|
||||||
|
handleCommand(inputRef.current.value)
|
||||||
|
inputRef.current.value = ''
|
||||||
|
setCurrentInput('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outputRef.current) {
|
||||||
|
setImmediate(() => outputRef.current.scrollTop = outputRef.current.scrollHeight)
|
||||||
|
setCurrentScroll(outputRef.current.scrollHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const playArea = textRef.current
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
inputRef.current.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
playArea.addEventListener('click', onClick)
|
||||||
|
return () => playArea.removeEventListener('click', onClick)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={textRef} className={styles.playArea}>
|
||||||
|
<div ref={outputRef} onScroll={() => setCurrentScroll(outputRef.current?.scrollTop)} className={styles.output}>
|
||||||
|
{messages.map((message, i) => {
|
||||||
|
if(message.type === 'message')
|
||||||
|
return <ReactMarkdown key={i}>{message.message}</ReactMarkdown>
|
||||||
|
|
||||||
|
if(message.type === 'command')
|
||||||
|
return <p key={i} className={styles.command}>{message.command}</p>
|
||||||
|
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<form className={styles.input} onSubmit={onSubmit}>
|
||||||
|
<input ref={inputRef} onChange={ev => setCurrentInput(ev.target.value)} id="gameInput"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<Reflection messages={messages} currentInput={currentInput} currentScroll={currentScroll}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
.playArea {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reflectedArea {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: scale(.14, .12);
|
||||||
|
margin-top: -3px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: .2;
|
||||||
|
filter: blur(2px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 16px;
|
||||||
|
padding-top: 0;
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input input {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
text-indent: 16px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command {
|
||||||
|
font-weight: bold;
|
||||||
|
position: relative;
|
||||||
|
text-indent: 16px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command::before {
|
||||||
|
position: absolute;
|
||||||
|
left: -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command::before, .input::before {
|
||||||
|
content: '> ';
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shadow
|
||||||
|
*/
|
||||||
|
.playArea {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function useWindowSize() {
|
||||||
|
const [windowSize, setWindowSize] = useState(getSize);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
setWindowSize(getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSize() {
|
||||||
|
return {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
};
|
||||||
|
}
|
@ -1,3 +1,16 @@
|
|||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: black;
|
||||||
|
/* font-family: 'VT323', monospace; */
|
||||||
|
font-family: 'Share Tech Mono', monospace;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue