Fx
This commit is contained in:
@@ -37,6 +37,8 @@ kotlin {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation("nl.astraeus:vst-worklet-base:1.0.0-SNAPSHOT")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package nl.astraeus.vst
|
||||
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.w3c.dom.MessagePort
|
||||
|
||||
enum class AutomationRate(
|
||||
val rate: String
|
||||
) {
|
||||
A_RATE("a-rate"),
|
||||
K_RATE("k-rate")
|
||||
}
|
||||
|
||||
interface AudioParam {
|
||||
var value: Double
|
||||
var automationRate: AutomationRate
|
||||
val defaultValue: Double
|
||||
val minValue: Double
|
||||
val maxValue: Double
|
||||
}
|
||||
|
||||
interface AudioParamMap {
|
||||
operator fun get(name: String): AudioParam
|
||||
}
|
||||
|
||||
abstract external class AudioWorkletProcessor {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/AudioWorkletNode/parameters) */
|
||||
//val parameters: AudioParamMap;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/AudioWorkletNode/port) */
|
||||
@JsName("port")
|
||||
val port: MessagePort
|
||||
|
||||
@JsName("process")
|
||||
open fun process (
|
||||
inputs: Array<Array<Float32Array>>,
|
||||
outputs: Array<Array<Float32Array>>,
|
||||
parameters: dynamic
|
||||
) : Boolean { definedExternally }
|
||||
|
||||
}
|
||||
|
||||
external fun registerProcessor(name: String, processorCtor: JsClass<*>)
|
||||
external val sampleRate: Int
|
||||
external val currentTime: Double
|
||||
@@ -66,6 +66,11 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
var waveform = Waveform.SINE.ordinal
|
||||
var dutyCycle = 0.5
|
||||
var fmFreq = 0.0
|
||||
var fmAmp = 0.0
|
||||
var amFreq = 0.0
|
||||
var amAmp = 0.0
|
||||
val sampleLength = 1 / sampleRate.toDouble()
|
||||
|
||||
init {
|
||||
this.port.onmessage = ::handleMessage
|
||||
@@ -106,6 +111,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
|
||||
private fun playMidi(bytes: Int32Array) {
|
||||
if (bytes.length > 0) {
|
||||
//console.log("Received", bytes)
|
||||
when(bytes[0]) {
|
||||
0x90 -> {
|
||||
if (bytes.length == 3) {
|
||||
@@ -119,6 +125,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x80 -> {
|
||||
if (bytes.length >= 2) {
|
||||
val note = bytes[1]
|
||||
@@ -126,6 +133,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
noteOff(note)
|
||||
}
|
||||
}
|
||||
|
||||
0xc9 -> {
|
||||
if (bytes.length >= 1) {
|
||||
val waveform = bytes[1]
|
||||
@@ -135,18 +143,44 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0xb0 -> {
|
||||
if (bytes.length == 3) {
|
||||
val knob = bytes[1]
|
||||
val value = bytes[2]
|
||||
|
||||
when(knob) {
|
||||
when (knob) {
|
||||
0x4a -> {
|
||||
dutyCycle = value / 127.0
|
||||
}
|
||||
|
||||
0x4b -> {
|
||||
fmFreq = value / 127.0
|
||||
}
|
||||
|
||||
0x4c -> {
|
||||
fmAmp = value / 127.0
|
||||
}
|
||||
|
||||
0x47 -> {
|
||||
amFreq = value / 127.0
|
||||
}
|
||||
|
||||
0x48 -> {
|
||||
amAmp = value / 127.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0xe0 -> {
|
||||
if (bytes.length == 3) {
|
||||
val lsb = bytes[1]
|
||||
val msb = bytes[2]
|
||||
|
||||
amFreq = (((msb - 0x40) + (lsb / 127.0)) / 0x40) * 10.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +201,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
notes[i].state = NoteState.ON
|
||||
|
||||
val n = Note.fromMidi(note)
|
||||
console.log("Playing note: ${n.sharp} (${n.freq})")
|
||||
//console.log("Playing note: ${n.sharp} (${n.freq})")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -205,13 +239,16 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
note.releaseSamples--
|
||||
targetVolume *= (note.releaseSamples / 10000f)
|
||||
}
|
||||
note.actualVolume += (targetVolume - note.actualVolume) * 0.001f
|
||||
note.actualVolume += (targetVolume - note.actualVolume) * 0.0001f
|
||||
|
||||
if (note.state == NoteState.RELEASED && note.actualVolume <= 0) {
|
||||
note.state = NoteState.OFF
|
||||
}
|
||||
|
||||
val cycleOffset = note.cycleOffset
|
||||
var cycleOffset = note.cycleOffset
|
||||
val fmModulation = sin(sampleLength * fmFreq * 10f * PI2 * note.sample).toFloat() * fmAmp * 5f
|
||||
val amModulation = 1f + (sin(sampleLength * amFreq * 10f * PI2 * note.sample) * amAmp).toFloat()
|
||||
cycleOffset += fmModulation
|
||||
|
||||
val waveValue: Float = when (waveform) {
|
||||
0 -> {
|
||||
@@ -233,13 +270,14 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
}
|
||||
|
||||
left[i] = left[i] + waveValue * note.actualVolume * 0.3f
|
||||
right[i] = right[i] + waveValue * note.actualVolume * 0.3f
|
||||
left[i] = left[i] + waveValue * note.actualVolume * 0.3f * amModulation
|
||||
right[i] = right[i] + waveValue * note.actualVolume * 0.3f * amModulation
|
||||
|
||||
note.cycleOffset += sampleDelta
|
||||
if (cycleOffset > 1f) {
|
||||
note.cycleOffset -= 1f
|
||||
}
|
||||
note.sample++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ enum class Note(
|
||||
;
|
||||
|
||||
// 69 = A4.ordinal
|
||||
val freq: Double = round(440.0 * 2.0.pow((ordinal - 69)/12.0) * 10000.0) / 10000.0
|
||||
val freq: Double = round(440.0 * 2.0.pow((ordinal - 69)/12.0)) // * 10000.0) / 10000.0
|
||||
val cycleLength: Double = 1.0 / freq
|
||||
var sampleDelta: Double = 0.0
|
||||
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
package nl.astraeus.vst.chip
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.browser.window
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.chip.channel.Broadcaster
|
||||
import nl.astraeus.vst.chip.channel.MidiMessage
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import org.khronos.webgl.Uint8Array
|
||||
|
||||
fun main() {
|
||||
Komponent.create(document.body!!, MainView)
|
||||
|
||||
Broadcaster.start()
|
||||
|
||||
Midi.start()
|
||||
|
||||
console.log("Performance", window.performance)
|
||||
Broadcaster.getChannel(0).postMessage(
|
||||
MidiMessage(
|
||||
Uint8Array(arrayOf(0x80.toByte(), 60, 60)),
|
||||
window.performance.now()
|
||||
)
|
||||
)
|
||||
|
||||
window.setInterval({
|
||||
Broadcaster.sync()
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class AudioModule(
|
||||
console.log("Module not yet loaded")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class AudioNode(
|
||||
|
||||
@@ -1,23 +1,104 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.chip.channel
|
||||
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import kotlinx.browser.window
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.w3c.dom.BroadcastChannel
|
||||
import org.w3c.dom.MessageEvent
|
||||
import kotlin.js.Date
|
||||
import kotlin.math.min
|
||||
|
||||
@JsExport
|
||||
enum class MessageType {
|
||||
SYNC,
|
||||
MIDI
|
||||
}
|
||||
|
||||
@JsExport
|
||||
class SyncMessage(
|
||||
@JsName("timeOrigin")
|
||||
val timeOrigin: Double = window.performance.asDynamic().timeOrigin,
|
||||
@JsName("now")
|
||||
val now: Double = window.performance.now()
|
||||
) {
|
||||
@JsName("type")
|
||||
val type: String = MessageType.SYNC.name
|
||||
}
|
||||
|
||||
// time -> syncOrigin
|
||||
// receive syn message
|
||||
// syncOrigin = my timeOrigin - sync.timeOrigin
|
||||
// - sync.timeOrigin = 50
|
||||
// - my.timeOrigin = 100
|
||||
// - syncOrigin = -50
|
||||
|
||||
// - sync.timeOrigin = 49
|
||||
// - my.timeOrigin = 100
|
||||
// - syncOrigin = update to -51
|
||||
|
||||
@JsExport
|
||||
class MidiMessage(
|
||||
@JsName("data")
|
||||
val data: Uint8Array,
|
||||
@JsName("timestamp")
|
||||
val timestamp: dynamic = null,
|
||||
@JsName("timeOrigin")
|
||||
val timeOrigin: dynamic = window.performance.asDynamic().timeOrigin
|
||||
) {
|
||||
@JsName("type")
|
||||
val type = MessageType.MIDI.name
|
||||
}
|
||||
|
||||
object Sync {
|
||||
var syncOrigin = 0.0
|
||||
|
||||
fun update(sync: SyncMessage) {
|
||||
syncOrigin = min(syncOrigin, window.performance.asDynamic().timeOrigin - sync.timeOrigin)
|
||||
}
|
||||
|
||||
fun now(): Double = window.performance.now() + syncOrigin
|
||||
}
|
||||
|
||||
object Broadcaster {
|
||||
val channels = mutableMapOf<Int, BroadcastChannel>()
|
||||
|
||||
val channel = BroadcastChannel("audio-worklet")
|
||||
fun getChannel(channel: Int): BroadcastChannel = channels.getOrPut(channel) {
|
||||
val bcChannel = BroadcastChannel("audio-worklet-$channel")
|
||||
|
||||
fun start() {
|
||||
channel.onmessage = ::onMessage
|
||||
bcChannel.onmessage = { event ->
|
||||
onMessage(channel, event)
|
||||
}
|
||||
|
||||
bcChannel
|
||||
}
|
||||
|
||||
fun onMessage(event: MessageEvent) {
|
||||
MainView.addMessage("Received message ${event.data} time ${Date().getTime()}")
|
||||
private fun onMessage(channel: Int, event: MessageEvent) {
|
||||
val data: dynamic = event.data.asDynamic()
|
||||
|
||||
if (data.type == MessageType.SYNC.name) {
|
||||
val syncMessage = SyncMessage(
|
||||
data.timeOrigin,
|
||||
data.now
|
||||
)
|
||||
Sync.update(syncMessage)
|
||||
} else {
|
||||
console.log(
|
||||
"Received broadcast message on channel $channel",
|
||||
event,
|
||||
window.performance,
|
||||
window.performance.now() - event.timeStamp.toDouble()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun send(message: String) {
|
||||
channel.postMessage(message)
|
||||
fun send(channel: Int, message: Any) {
|
||||
getChannel(channel).postMessage(message)
|
||||
}
|
||||
|
||||
fun sync() {
|
||||
for (channel in channels.values) {
|
||||
channel.postMessage(SyncMessage())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,13 +30,20 @@ external class MIDIOutput {
|
||||
val type: String
|
||||
val version: String
|
||||
|
||||
fun send(message: dynamic)
|
||||
fun send(message: dynamic, timestamp: dynamic)
|
||||
|
||||
fun open()
|
||||
fun close()
|
||||
}
|
||||
|
||||
object Midi {
|
||||
var inputChannel: Int = -1
|
||||
var outputChannel: Int = -1
|
||||
|
||||
var inputs = mutableListOf<MIDIInput>()
|
||||
var outputs = mutableListOf<MIDIInput>()
|
||||
var outputs = mutableListOf<MIDIOutput>()
|
||||
var currentInput: MIDIInput? = null
|
||||
var currentOutput: MIDIOutput? = null
|
||||
|
||||
fun start() {
|
||||
val navigator = window.navigator.asDynamic()
|
||||
@@ -68,7 +75,7 @@ object Midi {
|
||||
)
|
||||
}
|
||||
|
||||
fun setInput(input: MIDIInput) {
|
||||
fun setInput(input: MIDIInput?) {
|
||||
console.log("Setting input", input)
|
||||
currentInput?.close()
|
||||
|
||||
@@ -94,4 +101,17 @@ object Midi {
|
||||
currentInput?.open()
|
||||
}
|
||||
|
||||
fun setOutput(output: MIDIOutput?) {
|
||||
console.log("Setting output", output)
|
||||
currentOutput?.close()
|
||||
|
||||
currentOutput = output
|
||||
|
||||
currentOutput?.open()
|
||||
}
|
||||
|
||||
fun send(data: Uint8Array, timestamp: dynamic? = null) {
|
||||
currentOutput?.send(data, timestamp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,38 +6,39 @@ import daw.style.Css.noTextSelect
|
||||
import daw.style.CssId
|
||||
import daw.style.CssName
|
||||
import daw.style.hover
|
||||
import kotlinx.html.FlowContent
|
||||
import kotlinx.html.P
|
||||
import kotlinx.html.a
|
||||
import kotlinx.html.br
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import kotlinx.html.hr
|
||||
import kotlinx.html.input
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.html.js.onMouseDownFunction
|
||||
import kotlinx.html.js.onMouseUpFunction
|
||||
import kotlinx.html.option
|
||||
import kotlinx.html.select
|
||||
import kotlinx.html.span
|
||||
import nl.astraeus.css.properties.BoxSizing
|
||||
import nl.astraeus.css.properties.FontWeight
|
||||
import nl.astraeus.css.properties.Position
|
||||
import nl.astraeus.css.properties.Transform
|
||||
import nl.astraeus.css.properties.em
|
||||
import nl.astraeus.css.properties.hsla
|
||||
import nl.astraeus.css.properties.prc
|
||||
import nl.astraeus.css.properties.px
|
||||
import nl.astraeus.css.properties.rem
|
||||
import nl.astraeus.css.properties.vh
|
||||
import nl.astraeus.css.properties.vw
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.Note
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.channel.Broadcaster
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import org.khronos.webgl.Int32Array
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.performance.Performance
|
||||
|
||||
object MainView : Komponent() {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
private var started = false
|
||||
|
||||
init {
|
||||
MainViewCss
|
||||
@@ -52,94 +53,153 @@ object MainView : Komponent() {
|
||||
}
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
div {
|
||||
h1 {
|
||||
+"VST Chip"
|
||||
}
|
||||
div {
|
||||
+"Hello, World!"
|
||||
}
|
||||
div {
|
||||
if (VstChipWorklet.created) {
|
||||
+"Worklet created"
|
||||
} else {
|
||||
a {
|
||||
href = "#"
|
||||
+"Create worklet"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.create {
|
||||
requestUpdate()
|
||||
div(MainViewCss.MainDivCss.name) {
|
||||
if (!started) {
|
||||
div(MainViewCss.StartSplashCss.name) {
|
||||
div(MainViewCss.StartBoxCss.name) {
|
||||
div(MainViewCss.StartButtonCss.name) {
|
||||
+"START"
|
||||
onClickFunction = {
|
||||
started = true
|
||||
VstChipWorklet.create {
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
+"VST Chip"
|
||||
}
|
||||
div {
|
||||
+ "Midi input: "
|
||||
select {
|
||||
for (mi in Midi.inputs) {
|
||||
span {
|
||||
+"Midi input: "
|
||||
select {
|
||||
option {
|
||||
+mi.name
|
||||
value = mi.id
|
||||
+"None"
|
||||
value = ""
|
||||
}
|
||||
option {
|
||||
+"Midi over Broadcast"
|
||||
value = "midi-broadcast"
|
||||
}
|
||||
for (mi in Midi.inputs) {
|
||||
option {
|
||||
+mi.name
|
||||
value = mi.id
|
||||
}
|
||||
}
|
||||
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
if (target.value == "") {
|
||||
Midi.setInput(null)
|
||||
} else {
|
||||
val selected = Midi.inputs.find { it.id == target.value }
|
||||
if (selected != null) {
|
||||
Midi.setInput(selected)
|
||||
} else if (target.value == "midi-broadcast") {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
val selected = Midi.inputs.find { it.id == target.value }
|
||||
if (selected != null) {
|
||||
Midi.setInput(selected)
|
||||
}
|
||||
span {
|
||||
+"channel:"
|
||||
input {
|
||||
type = InputType.number
|
||||
value = Midi.inputChannel.toString()
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
Midi.inputChannel = target.value.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
span {
|
||||
+"Midi output: "
|
||||
select {
|
||||
option {
|
||||
+"None"
|
||||
value = ""
|
||||
}
|
||||
option {
|
||||
+"Midi over Broadcast"
|
||||
value = "midi-broadcast"
|
||||
}
|
||||
for (mi in Midi.outputs) {
|
||||
option {
|
||||
+mi.name
|
||||
value = mi.id
|
||||
}
|
||||
}
|
||||
|
||||
br {}
|
||||
|
||||
hr {}
|
||||
|
||||
repeat(9) {
|
||||
div(classes = MainViewCss.NoteBarCss.name) {
|
||||
for (index in it*12+12..it*12+23) {
|
||||
notePlayer(Note.entries[index])
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
if (target.value == "") {
|
||||
Midi.setOutput(null)
|
||||
} else {
|
||||
val selected = Midi.outputs.find { it.id == target.value }
|
||||
if (selected != null) {
|
||||
Midi.setOutput(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"channel:"
|
||||
input {
|
||||
type = InputType.number
|
||||
value = Midi.outputChannel.toString()
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
Midi.outputChannel = target.value.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
for (message in messages) {
|
||||
div {
|
||||
+message
|
||||
div {
|
||||
+"Send note on to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x70.toByte()
|
||||
)
|
||||
)
|
||||
Midi.send(data, window.performance.now() + 1000)
|
||||
Midi.send(data, window.performance.now() + 2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FlowContent.notePlayer(note: Note) {
|
||||
span {
|
||||
a(classes = MainViewCss.ButtonCss.name) {
|
||||
href = "#"
|
||||
+note.sharp
|
||||
onMouseDownFunction = {
|
||||
VstChipWorklet.postMessage(Int32Array(arrayOf(0x90, note.ordinal, 32)))
|
||||
div {
|
||||
+"Send note off to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x0.toByte(),
|
||||
)
|
||||
)
|
||||
Midi.send(data)
|
||||
}
|
||||
onMouseUpFunction = {
|
||||
VstChipWorklet.postMessage(Int32Array(arrayOf(0x90, note.ordinal, 0)))
|
||||
}
|
||||
/*
|
||||
onMouseOutFunction = {
|
||||
VstChipWorklet.postMessage(Int32Array(arrayOf(0x90, note.ordinal, 0)))
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MainViewCss : CssId("main") {
|
||||
object MainDivCss : CssName()
|
||||
object ActiveCss : CssName()
|
||||
object ButtonCss : CssName()
|
||||
object NoteBarCss : CssName()
|
||||
object StartSplashCss : CssName()
|
||||
object StartBoxCss : CssName()
|
||||
object StartButtonCss : CssName()
|
||||
|
||||
init {
|
||||
defineCss {
|
||||
@@ -155,9 +215,6 @@ object MainView : Komponent() {
|
||||
padding(0.px)
|
||||
height(100.prc)
|
||||
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
|
||||
fontFamily("JetbrainsMono, monospace")
|
||||
fontSize(14.px)
|
||||
fontWeight(FontWeight.bold)
|
||||
@@ -180,6 +237,49 @@ object MainView : Komponent() {
|
||||
select(cls(NoteBarCss)) {
|
||||
minHeight(4.rem)
|
||||
}
|
||||
select(cls(MainDivCss)) {
|
||||
margin(1.rem)
|
||||
}
|
||||
select("select") {
|
||||
plain("appearance", "none")
|
||||
border("0")
|
||||
outline("0")
|
||||
width(20.rem)
|
||||
padding(0.5.rem, 2.rem, 0.5.rem, 0.5.rem)
|
||||
backgroundImage("url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg')")
|
||||
background("right 0.8em center/1.4em")
|
||||
backgroundColor(Css.currentStyle.inputBackgroundColor)
|
||||
color(Css.currentStyle.entryFontColor)
|
||||
borderRadius(0.25.em)
|
||||
}
|
||||
select(cls(StartSplashCss)) {
|
||||
position(Position.fixed)
|
||||
left(0.px)
|
||||
top(0.px)
|
||||
width(100.vw)
|
||||
height(100.vh)
|
||||
zIndex(100)
|
||||
backgroundColor(hsla(32, 0, 50, 0.6))
|
||||
|
||||
select(cls(StartBoxCss)) {
|
||||
position(Position.relative)
|
||||
left(25.vw)
|
||||
top(25.vh)
|
||||
width(50.vw)
|
||||
height(50.vh)
|
||||
backgroundColor(hsla(0, 0, 50, 0.25))
|
||||
|
||||
select(cls(StartButtonCss)) {
|
||||
position(Position.absolute)
|
||||
left(50.prc)
|
||||
top(50.prc)
|
||||
transform(Transform("translate(-50%, -50%)"))
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
cursor("pointer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user