Styling!
parent
01dcae094a
commit
64a0c4e0b5
@ -1,72 +1,29 @@
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
.screen {
|
||||
display: flex;
|
||||
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;
|
||||
left: -1px;
|
||||
font-weight: bold;
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
background: black;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.input::before {
|
||||
.overlay {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
}
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
.infoArea {
|
||||
width: 300px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.command {
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
text-indent: 16px;
|
||||
white-space: pre;
|
||||
opacity: .4;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.command::before {
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
}
|
||||
|
||||
.command::before, .input::before {
|
||||
content: '> ';
|
||||
font-weight: bold;
|
||||
.overlay img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: brightness(2.5);
|
||||
}
|
||||
|
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 {
|
||||
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