mixin layerR(n, m, c, groups)
  if --n
    - let start = n % 4 == 0 ? 'start' : ''
    input(type='checkbox' class=`touch${n} ${start}`).touch
    - let end = m % 4 == 0 ? 'end' : ''
    .layer(class=`layer${n} ${end}`)
      if m % 4 == 0 
        a(href='#container').clear
        .timer
      if n == 1
        input(type='checkbox' disabled).touch.start
        .morse-code
          each g in groups
            - for (l = 1; l <= 4; l++)
              .code(class=`code-${g}-${l}`) 
        .translation
          each g in groups
            .letter(class=`letter-${g}`)   

      +layerR(n, ++m, c, groups)
      
mixin layer(n)
  input(type='reset' id='reset').reset
  a(href='#begin' id='begin').begin
  a(href='#container').clear
  .cleared
  - let groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
  +layerR(n + 1, 1, n, groups)
  .warning

form.container(id='container')
  +layer(40)
View Compiled
@import url('https://fonts.googleapis.com/css2?family=Open+Sans&family=Roboto+Mono&display=swap')

html
  width: 100vw
  height: 100vh
  body
    width: 100%
    height: 100%
    display: flex
    box-sizing: border-box
    touch-action: none
    *,*::before,*::after
      box-sizing: inherit
      touch-action: inherit

@keyframes hide
  0%
    opacity: 1
    --position: initial
  99.999%
    opacity: 1
    --position: initial
  100%
    opacity: 0
    --position: absolute
  
@keyframes reveal
  0%
    opacity: 0
    --position: absolute
  99.999%
    opacity: 0
    --position: absolute
  100%
    opacity: 1
    --position: initial

@keyframes enable
  0%
    pointer-events: none
  99.999%
    pointer-events: none
  100%
    pointer-events: auto 
  
@keyframes disable
  0%
    color: black
  0.001%
    color: grey
  100%
    color: grey

$groups: (A, B, C, D, E, F, G, H, I, J)
@mixin arrange($groups: $groups)
  @each $g in $groups
    @for $l from 1 through 4
      @keyframes touch-#{$g}-#{$l}
        0%
          --code-#{$g}-#{$l}: var(--dot)
          --LEFT-#{$g}-#{$l}: initial 
          --RIGHT-#{$g}-#{$l}: #{' '}
        25%
          --code-#{$g}-#{$l}: var(--dash)
          --LEFT-#{$g}-#{$l}: #{' '}
          --RIGHT-#{$g}-#{$l}: initial
        100%
          --code-#{$g}-#{$l}: var(--dash)
          --LEFT-#{$g}-#{$l}: #{' '}
          --RIGHT-#{$g}-#{$l}: initial

$min: 2s
$extra: $min / 8
@mixin layerR($n, $m, $l, $i, $c, $groups: $groups, $min: $min, $extra: $extra)
  @if $n != 0
    $g: nth($groups, $i)
    .touch#{$n}
      @if $n % 4 == 0
        &.start 
          & ~ .layer#{$n}
            --start-#{$g}: paused
          &:checked ~ .layer#{$n}
            --start-#{$g}: running
      & ~ .layer#{$n}
        --state#{$n}: paused
        --checked-#{$g}-#{$l}: #{' '}
        --LEVEL-#{$g}-#{$l}: #{' '}
        --touched-#{$g}-#{$l}: + 0s
      &:active ~ .layer#{$n}
        --state#{$n}: running
        --start-#{$g}: paused
      &:checked ~ .layer#{$n}
        --checked-#{$g}-#{$l}: initial
        --LEVEL-#{$g}-#{$l - 1}: #{' '}
        --LEVEL-#{$g}-#{$l}: initial
        --touched-#{$g}-#{$l}: + #{$extra}

    .layer#{$n}
      $animation: ()
      $animation: append($animation, touch-#{$g}-#{$l} #{$min} var(--state#{$n}) linear forwards, comma)
      animation: $animation
      @if $m % 4 == 0
        $time: ($min)
        @for $l from 1 through 4
          $time: append($time, var(--touched-#{$g}-#{$l}))
        &.end
          --touched-#{$g}: calc(#{$time})
          animation: append($animation, enable var(--touched-#{$g}) var(--start-#{$g}) linear forwards, comma)
          &::before
            animation: reveal var(--touched-#{$g}) var(--start-#{$g}) linear forwards
          & > .start
            animation: reveal var(--touched-#{$g}) var(--start-#{$g}) linear forwards
          $animation: ()
          @if $i - 1 != 0
            $p: nth($groups, $i - 1)
            $animation: append($animation, reveal var(--touched-#{$p}) var(--start-#{$p}) linear forwards, comma)
          & > .timer
            animation: append($animation, expire var(--touched-#{$g}) var(--start-#{$g}) linear, comma)
            &::before
              animation: flash var(--touched-#{$g}) var(--start-#{$g}) linear backwards
          & > .clear
            $animation: append($animation, disable var(--touched-#{$g}) var(--start-#{$g}) linear backwards, comma)
            animation: $animation

    @include layerR($n - 1, $m + 1, if($m % 4 == 0, 1, $l + 1), if($m % 4 == 0, $i + 1, $i), $c)
    
@mixin layer($n, $groups: $groups)
  --dot: '\2022'
  --dash: '\002D'
  @include layerR($n, 1, 1, 1, $n)
  @each $g in $groups
    @for $l from 1 through 4
      .code-#{$g}-#{$l}
        animation: hide var(--touched-#{$g}) var(--start-#{$g}) linear forwards
        &::before
          content: var(--checked-#{$g}-#{$l}, var(--code-#{$g}-#{$l}))
    .letter-#{$g}
      @include binary-tree($g)
      animation: reveal var(--touched-#{$g}) var(--start-#{$g}) linear forwards
       
@mixin grid-area
  display: grid
  grid-template:
    columns: 1fr
    rows: repeat(3,1fr)
    areas: 'translation' 'morse-code' 'touch'
      
$values: (ROOT,E,T,I,A,N,M,S,U,R,W,D,K,G,O,H,V,F,'',L,'',P,J,B,X,C,Y,Z,Q,'','')    
@mixin binary-tree($group, $values: $values)
  $level: 1
  $count: 0
  $max: 1
  $length: length($values)
  --LEVEL-#{$group}-0: #{' '}
  @for $i from 1 through length($values)
    $value: nth($values, $i)
    $left: 2 * $i
    $right: 2 * $i + 1 
    --#{$value}-VALUE: '#{$value}'
    @if $left <= $length
      --#{$value}-LEFT: var(--LEFT-#{$group}-#{$level}, var(--#{nth($values, $left)}-NODE))
    @else
      --#{$value}-LEFT: #{' '}
    @if $right <= $length
      --#{$value}-RIGHT: var(--RIGHT-#{$group}-#{$level}, var(--#{nth($values, $right)}-NODE))
    @else
      --#{$value}-RIGHT: #{' '}
    --#{$value}-NODE: var(--LEVEL-#{$group}-#{$level - 1}, var(--#{$value}-VALUE)) var(--#{$value}-LEFT) var(--#{$value}-RIGHT)
    $count: $count + 1
    @if $count == $max
      $count: 0
      $max: $max * 2
      $level: $level + 1
  --LETTER: var(--ROOT-NODE)
    
.container
  @include grid-area
  --screen: 100vmin
  --break-point: clamp(0px, var(--screen) - 500px, 50vmin)
  --width: calc(var(--screen) - var(--break-point)) 
  width: var(--width)
  font-size: var(--width)
  height: calc(var(--width) * 1.2)
  border: 0.0175em solid grey
  background-color: white
  border-radius: 5% / 4%
  overflow: hidden
  margin: auto
  position: relative
  font-family: 'Open Sans', sans-serif
  @include layer(40)
  @include arrange
  &:target 
    *:not(.begin), *::before
      animation: unset !important
    .layer
      pointer-events: none !important
      opacity: 0
    .cleared, .reset
      visibility: visible
      
.layer
  @include grid-area
  width: 100%
  height: 100%
  position: absolute
  z-index: 3
  &:not(.end)
    pointer-events: none
    &::before
      opacity: 0
  & > .touch:not(.start):not(:disabled)
    opacity: 0
  &::before
    content: ''
    width: 100%
    height: 100%
    position: absolute
    pointer-events: none
    background-color: white
  
.cleared
  width: 100%
  height: 100%
  position: absolute
  background-color: white
  visibility: hidden
  display: flex
  justify-content: center
  align-items: center
  &::after
    content: 'CLEARED'
    transform: translatey(-300%)
    font-size: 12.5%
    color: black
    z-index: 1

.begin
  background-color: white
  position: absolute
  width: 100%
  height: 100%
  display: flex
  justify-content: center
  align-items: center
  animation: activate 1ms paused forwards
  text-decoration: none
  &:target
    pointer-events: none
    animation: unset
    &::before, &::after
      opacity: 0
  &:not(:target) 
    & ~ :is(.touch, .layer)
      opacity: 0
    & ~ .clear
      visibility: hidden
  &::after
    font-size: 12.5%
    content: 'Begin'
    color: black
    border: 0.25em solid transparent
    box-shadow: inset 0 0 0 0.1em grey, 0 0 0 0.1em grey
    width: calc(var(--width) / 2)
    height: calc(var(--width) / 2)
    border-radius: 100%
    display: flex
    justify-content: center
    align-items: center
  @keyframes activate
    0%
      --z-index: 0
    100% 
      --z-index: 2
  
.clear
  font-size: 6.5%
  position: absolute
  bottom: 22.5%
  right: 7.5%
  width: calc(var(--width) / 5)
  height: calc(var(--width) / 5)
  border-radius: 100%
  border: 0.12em solid grey
  background-color: lightgrey
  text-decoration: none
  display: flex
  justify-content: center
  align-items: center
  color: black
  &::before
    content: 'clear'
    transform: translatey(-5%)
  
.reset
  visibility: hidden
  position: absolute
  font-size: 12.5%
  font-family: inherit
  border-radius: 100%
  border: 0.25em solid grey
  width: calc(var(--width) / 2)
  height: calc(var(--width) / 2)
  top: 50%
  left: 50%
  transform: translate(-50%, -50%)
  z-index: 1
  background-color: white
  color: black
  cursor: pointer
  &:focus
    outline: none
  &:active ~ .begin
    animation-play-state: running
  &:not(:active) ~ .begin
    z-index: var(--z-index)
    
.timer
  position: absolute
  bottom: 22.5%
  left: 7.5%
  width: calc(var(--width) / 5)
  height: calc(var(--width) / 5)
  border-radius: 100%
  border: 0.12em solid grey
  background-color: lightgrey
  background-image: conic-gradient(grey var(--degree), transparent var(--degree) 360deg)
  display: flex
  justify-content: center
  align-items: center
  font-size: 6.5%
  @keyframes expire
    $p: 100% / 360 
    @for $i from 0 through 360
      #{$p * $i}
        --degree: #{$i * 1deg}
  &::before
    content: 'DECODING'
    font-size: 45%
    transform: translatey(7.5%)
    opacity: 0
    @keyframes flash
      @for $i from 0 through 6
        #{$i * (100% / 6)}
          opacity: #{$i % 2}

%array
  display: flex
  justify-content: center
  align-items: center
  border: 0.01em solid grey
  background-color: rgba(0,0,0,0.05)  
 
.morse-code
  @extend %array
  grid-area: morse-code
  margin: 5% 17.5%
  height: calc(var(--width) / 5)
  border-radius: 7.5% / 27.5%
  z-index: 4
  
.translation
  @extend %array
  grid-area: translation
  margin: 5% 5% 0
  height: calc(var(--width) / 3)
  border-radius: 7.5% / 17.5%
  z-index: 4
  
%character
  flex: 0 1
  position: var(--position)

.code
  @extend %character
  font-size: 37.5%
  font-family: initial
  
.letter
  @extend %character
  font-size: 12.5%
  font-family: 'Roboto Mono', monospace
  &::before
    content: var(--LETTER)

.touch
  width: calc(var(--width) / 3)
  height: calc(var(--width) / 3)
  border-radius: 100%
  justify-self: center
  align-self: center
  appearance: none
  grid-area: touch
  border: 0.5em solid grey
  background-color: lightgrey
  z-index: 2
  transform: translatey(-15%)
  position: relative
  &::before
    content: ''
    width: 100%
    height: 100%
    position: absolute
    border-radius: inherit
    pointer-events: none
    background-color: transparent
    box-shadow: inset 0 0 1em black, 0 0 0.5em black
    opacity: 0
    transition: 300ms opacity
  &:active::before
    opacity:1
  &:not(:disabled)
    cursor: pointer
  &:focus
    outline: none
  &:checked
    opacity: 0
    pointer-events: none
    & ~ .layer
      &:not(.end)
        pointer-events: auto
        &::before
          opacity: 1
      & > .touch
        opacity: 1
        
.warning
  font-size: 3.5%
  z-index: 99
  width: 100%
  height: 100%
  background-color: white
  position: absolute
  display: flex
  justify-content: center
  align-items: center
  --message: 'Warning! This app is not accessible from ' var(--browser) ' browsers. Please try again from a Chrome desktop browser.'
  &::before
    content: var(--message)
    flex: 0 0 80%
    text-align: center
  @media screen and (-webkit-min-device-pixel-ratio: 0) and (min-resolution: .001dpcm) 
    visibility: hidden
  @supports (-moz-appearance: none)
    visibility: visible
    --browser: 'Firefox'
  @supports (-ms-ime-align: auto)
    visibility: visible
    --browser: 'Microsoft Edge'
  @media not all and (min-resolution: .001dpcm)
    @supports (-webkit-appearance: none) and (stroke-color: transparent)
      visibility: visible
      --browser: 'Safari'
  @media (pointer: coarse)
    visibility: visible
    --browser: 'mobile'
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.