Would there be a way for, say, a website to be able to generate fresh addresses (public key hashes) for each transaction without the website having access to the the private keys?
A secret key, k, is a 256-bit number and the corresponding public key is kG, where G is the generator of our elliptic curve group.
If you want a website to generate an address to receive funds, use a PRNG that seeds itself with a (unique) transaction number and a (global) secret known to the webserver and the back-end wallet. This PRNG will generate a (probably) unique random number r. Take r and construct a new public key kG + rG = (k + r)G. You can now construct a Bitcoin address that is (probably) unique to this transaction by taking the correct hash of the public key (k + r)G.
This is secure. The webserver knows kG and r, but not k. Because of the properties of elliptic curves, we can't figure out k given kG and (k + r)G. Therefore the webserver does not have access to the funds in (k + r)G.
The back-end wallet knows k and the global secret. Thus, if you give the back-end wallet the (unique) transaction number, it can re-construct r. This gives the back-end wallet both k and r, thus k + r, and hence the secret key for the public key (k + r)G.
This is the math behind your question anyway. There is existing code out there that already does (essentially) this. In general, this type of thing will be made easier when BIP0032, Hierarchical Deterministic Wallets, is implemented.