1. This is okay, but low-side switching will provide more voltage to drive high-brightness LEDs and makes resistor calculation 'simpler' since the switching device won't contribute much voltage drop. You can make the circuit one component simpler by using a MOSFET instead and omitting the base resistor.
2. As mentioned you need to switch the low side here if you want to use 'N' devices. I think an opto is overkill for experimenting, but any dedicated I/O board could use them, and probably should in an education context.
3. Does the GPIO include pull-up/down resistors? 'Smooth' switching is generally not desirable on logic inputs, and can cause serious problems in some situations. Inputs should always traverse the Vil(max)->Vih(min) no-mans-land as quickly as possible. There are better - but more complicated - debouncing solutions available to solve this in hardware, but it's better dealt with in software IMO, so this circuit could simply be a switch from GPIO to Vcc/GND.
4. I would suggest a comparator and potentiometer to set the reference level instead. I know you want to avoid ICs, but I think (basic) comparator action is about as simple as it gets once you need to deal with the analogue world. Hysteresis is probably a good idea, and less easily explained, though...
Use 3V3 for the input circuits and 5V for the output circuits. Though socketed buffers are highly advised for those who don't know what they're doing



