g | x | w | all
Bytes Lang Time Link
019Jelly250621T161929ZUnrelate
133C gcc250505T021000Za stone
022Jelly250507T195517ZJonathan
086JavaScript V8250504T173518ZArnauld
03105AB1E250506T083320ZKevin Cr
093Python250505T184622ZAlbert.L
087Ruby250504T215018ZLevel Ri
162Google Sheets250504T174535Zdoubleun
034Charcoal250504T174012ZNeil

Jelly, 19 bytes

2p5Ḋ>þ¤Ẏ€ị⁾.oUp$K€G

Try it online!

Took some trial and error to beat 22, to the point that it took me several minutes to even notice this worked when I threw it together with half-baked ideas to "fix" it back to another 22 already in my head!

    >þ                 Table greater-than over rows from
  5Ḋ                   [2 .. 5]
  5   ¤                and columns from [1 .. 5].
2p                     Cartesian product of [1, 2] with that.
       Ẏ€              Flatten the pairs,
         ị⁾.o          and modular 1-index them into ".o":
2p  >þ   ị⁾.o          0 becomes 'o', 1 becomes '.', and 2 becomes 'o'.
             Up        Take the Cartesian product of those reversed results with
               $       the un-reversed results.
                K€     Join each of those pairs on a space,
                  G    and grid format.

C (gcc), 135 133 bytes

-2 from @ceilingcat by using Unicode tricks
Includes a "direction change character" (0x202e) and a "digit shape selection character" (0x206f)

k;l(i){printf(L"‮"+"_^<XPONLH@"[i]/k%2);}f(i){for(i=100;i--;puts("")){for(k=1;k<32;)k*=l(i/10);for(printf("  ");k/=2;)l(i%10);}}

Try it online! Each line has 1 trailing space, and there is 1 trailing newline at the end.

Ungolfed/Explained (old version):

k;  // finger is stored here
l(i) {
    printf("%c ", ".o"["_^<XPONLH@"[i] >> k & 1]);
    // "_^<XPONLH@" is a bitmask that tells which fingers should be up
    // for each value. This finds the current digit value in the bitmask,
    // compares it to the current finger, and outputs accordingly
    // There's probably a better way to do it than this...
    // (like @Arnauld's solution: https://codegolf.stackexchange.com/a/279501/)
}
f(i) {
    for(i = 100; i--; puts("")) {  // For each possible combination, (output newline after iteration)
        for(k = 0; k<5; k++)       // For each finger from 0 to 4 on the left hand:
            l(i/10);               //     print expected output
        for(printf("  "); k--;)    // For each finger from 4 to 0 on the left hand:
            l(i%10);               //     print expected output
    }
}

Jelly, 22 bytes

⁾.o,Ɱṗ4ṢƑƇUƬƊKŒpżḢFƊ€G

A niladic Link that yields a list of characters, or a full program that prints to stdout.

Try it online!

How?

⁾.o,Ɱṗ4ṢƑƇUƬƊKŒpżḢFƊ€G - Link: no arguments
⁾.o                    - set the left argument to ".o"
            Ɗ          - last three links as a monad - f(".o"):
     ṗ4                -   Cartesian power 4 -> All lists of '.' and 'o' of length four
         Ƈ             -   keep those for which:
        Ƒ              -     is invariant under?:
       Ṣ               -       sort
                            -> ["....", "...o", "..oo", ".ooo", "oooo"]
           Ƭ           -   collect up while distinct under:
          U            -     reverse each
                            -> [["....", "...o", "..oo", ".ooo", "oooo"],
                                ["....", "o...", "oo..", "ooo.", "oooo"],
                               ]
    Ɱ                  - map across that with:
   ,                   -   {".o"} paired with {element}
                          -> [[".o",
                               ["....", "...o", "..oo", ".ooo", "oooo"],
                              ],
                              [".o",
                               ["....", "o...", "oo..", "ooo.", "oooo"],
                              ],
                             ]
             K         - join with space characters (well, one in this case)
                          -> [".o",
                              ["....", "...o", "..oo", ".ooo", "oooo"],
                              ' ',
                              ".o",
                              ["....", "o...", "oo..", "ooo.", "oooo"],
                             ]
              Œp       - Cartesian product
                          -> [['.', ".....", ' ', '.', "....."],
                              ['.', ".....", ' ', '.', "o...."],
                              ['.', ".....", ' ', '.', "oo..."],
                              ...
                              ['o', "ooooo", ' ', 'o', "ooo.."],
                              ['o', "ooooo", ' ', 'o', "oooo."],
                              ['o', "ooooo", ' ', 'o', "ooooo"],
                             ]
                    €  - for each:
                   Ɗ   -   last three links as a monad:
                 Ḣ     -     remove the head and yield it
                ż      -     {TheRest} zip with {that}
                  F    -     flatten
                           e.g. ['o', ".....", ' ', 'o', "....."]
                             -> ".....o o....."
                     G - format as a grid

JavaScript (V8), 86 bytes

A full program.

for(n=~99;q=9,n++;)print(...[..."12345 54321"].map(c=>+c?n/~q%(5<<c/5)<c?"o":".":q=c))

Try it online!

Commented

for(                 // main loop:
  n = ~99;           //   start with n = -100 and count upwards
  q = 9,             //   at each iteration, start with q = 9
  n++;               //   stop when n = 0
)                    //
print(               // print:
  ...                //   spread the result of map()
  [..."12345 54321"] //   go from 1 to 5 for 10's
                     //   and then from 5 to 1 for units
  .map(c =>          //   for each value c:
    +c ?             //     if c is not a space:
      n / ~q %       //       divide n by ~q (-10 or -1)
      (5 << c / 5)   //       reduce modulo 10 if c = 5
                     //       or modulo 5 otherwise
      < c ?          //       if it's less than c:
        "o"          //         output "o"
      :              //       else:
        "."          //         output "."
    :                //     else (central column):
      q = c          //       output " " and make q zero'ish
  )                  //   end of map()
)                    // end of print()

C (gcc), 108 bytes

-2 thanks to ceilingcat

Essentially the same algorithm. But each line ends with a trailing space.

q,i,c;f(n){for(n=~99;q=9,n++;puts(""))for(i=6;c=6-abs(--i);)printf("%c ",c>5?q=0,32:".o"[n/~q%(5<<c/5)<c]);}

Try it online!

05AB1E, 31 bytes

„.o5LÐ䛀ÁsDδ@«TиD{ís‚øèðδ.ý€˜»

Try it online.

Explanation:

„.o         # Push string ".o", which we'll use later on
5L          # Push list [1,2,3,4,5]
  Ð         # Triplicate it
   δ        # Pop two, and apply double-vectorized:
    ›       #  Larger than check
     €      # Map over each row:
      Á     #  Rotate it once clockwise
  s         # Swap to get the remaining list
   D        # Duplicate it again
    δ       # Pop both, and apply double-vectorized again:
     @      #  Larger than or equals check
        «   # Merge the two matrices together
         Tи # Repeat it 10 times
D           # Duplicate this list of 100 rows
 {í         # Sort each row of this copy in reversed order
   s‚       # Swap and pair the two list of 100 rows together
     ø      # Zip/transpose; swapping rows/columns,
            # to get 100 pairs of rows of 5 bits each
      è     # 0-based index each bit into the ".o"
 δ          # Map over each pair of rows:
ð .ý        #  Intersperse it with a space character
    €       # Map over each again:
     ˜      #  Flatten it to a list of 11 items
      »     # Then join each inner list with space-delimiter;
            # and then all strings with newline-delimiter
            # (after which the result is output implicitly)

5LÐ䛀Á results in matrix:

[[0,0,0,0,0],
 [0,1,0,0,0],
 [0,1,1,0,0],
 [0,1,1,1,0],
 [0,1,1,1,1]]

sDδ@ results in matrix:

[[1,0,0,0,0],
 [1,1,0,0,0],
 [1,1,1,0,0],
 [1,1,1,1,0],
 [1,1,1,1,1]]

Python, 93 bytes

a=['.o'[x>4]+x%5*'o'+~x%5*"."for x in range(10)]
[print(*x[::-1]+' '+y)for x in a for y in a]

Attempt This Online!

Originally, I missed the intermittent spaces:

Python, 88 bytes

a=['.o'[x>4]+x%5*'o'+~x%5*"."for x in range(10)]
[print(x[::-1],y)for x in a for y in a]

Attempt This Online!

Ruby, 87 bytes

f=->i{". "*(4-j=i%5)+"o "*j+".o"[i/5]}
100.times{|k|puts f[k/10]+"   "+f[k%10].reverse}

Try it online!

String manipulation seems the best way to go.

Ruby, 99 bytes

100.times{|i|puts ("%05b %05b"%[62<<i/10%5&31|1-i/50,15>>i%5|1-i%10/5<<4]).tr("01","o.").chars*" "}

Try it online!

This alternate approach is bitshift, convert to text representation of binary, and format. Didn't work out as short as I had hoped.

Google Sheets, 162 bytes

=index(let(a,". . . .",b,"o o o o",tocol(torow({a,". . . o",". . o o",". o o o",b}&{" .";" o"})&"   "&tocol({". ";"o "}&{a,"o . . .","o o . .","o o o .",b}),,1)))

Basic string concatenation using vertical and horizontal arrays with alternating tocol() and torow() transformations.

screenshot

Charcoal, 34 bytes

≔⪪”{∨8≔➙↓@}▶νI”χυ↑Eυ⭆ι×λχ→↑×⮌υχUE¹

Try it online! Link is to verbose version of code. Explanation:

≔⪪”{∨8≔➙↓@}▶νI”χυ

Get a list of strings o....o.... oo...oo... ooo..ooo.. oooo.oooo. ooooo...... (Boring I know, but my arithmetical approaches came out up to 50% longer.)

↑Eυ⭆ι×λχ

Print each string vertically, repeating each character 10 times.

Leave an extra column between each half.

↑×⮌υχ

Print the strings vertically in reverse order, repeating each string 10 times.

UE¹

Leave more extra columns.