The weakest definition of functional is that functions are a first-class object that can be passed around. This is the one that C conforms to in that old sense. This is a dead definition because almost everything in modern use conforms to this definition, and definitions are only useful to the extent they create distinct categories. Because almost every modern language has this, it can be difficult to imagine a language in which this is not true. But, yes, once upon a time, languages did not in general have a data type that could contain a function that you could call. (This is before my time, but I caught the tail end of these arguments.)

This was also one of the reasons that assembly programmers were always banging on about the power of assembly back in the day. Nowadays the only remnant of that argument is the claim that you can write more optimal assembly than the compiler. But back in the day, assembly programmers enjoyed the ability to have a pointer to a function and jump to it and/or call it (it's a bit fuzzier in assembler) and people using high-level languages were definitely getting a "weaker" experience. Today we expect our high level languages to also provide us significant power advantages over assembler. (Of course you can do anything in assembler, but not as concisely necessarily.)

When I got into computing, the definition of "functional" that excluded C included having "closures". This is a function pointer + an environment for the function to run in. C only has the function pointer; you don't get an environment. It is more convenient than nothing, but vastly less useful than a full closure. (You can do them manually, but they become problematic fast.)

Stepping up from there, you got languages that generally permitted an imperative style of programming, but "preferred" what we would today call a functional style, when you use map, filter, and such to perform operations. These languages loved them some linked lists; linked lists everywhere. With their own special names like "cons lists". They also tended to be garbage collected, which for a while was a "functional programming" thing, but is now also simply an accepted thing that a language may be, regardless of how "functional" it is.

This definition is still in some usage today, though some improvement in understanding the structure of the relevant of code ("iteration" as a concept you can abstract, rather than accidentally conflating "a linked list" with "the thing you can iterate on") and the fact that hardware is getting ever-more grumpy about non-locality has erased the linked list obsession. You can either have a "functional language" like Lisp, or you can program in a "functional style" in a multi-paradigm language like Javascript. In the latter case, you can do a lot of work with the functional paradigm, but technically you always end back up at structured programming with some flavor of OO, which is the dominant language paradigm. (Languages can be multi-paradigm, but there is always a dominant paradigm, one that when the paradigms conflict, is the winner. And my personal default definition of OO includes Javascript, considering the prototype system a detail relative to the fact you still have "collections of data with associated methods".) People who say that "Javascript is a functional language" mean this definition.

Finally, there's the Haskell definition. Here, immutability is the default, and possibly the only option. Type systems are very strong, able to express types like "a block of code that does not do any IO" that other languages can not express, or can only do very laboriously. You get "functor" and "monad" and such being not just demonstrated on a one-off basis, but being the foundational abstractions of libraries and entire applications. People argue over how much category theory you have to know to practically use these languages. F#, O'Caml, and Haskell live here. Haskell is as far as you can currently go in this direction and get a language usable for practical tasks, work that you can build a business on.

(As an interesting side bar, I think Erlang made an error here, although a perfectly understandable one. When it was written, one of the reasons immutability was favored at the academic level was that it helped write multi-core code. At the time, only big industry and academia had multi-core systems. But you only really need isolation between threads. Immutability is one way to achieve this, but you can also make it so that it is impossible to communicate "references" between processes/threads, so everything is a copy. Within an Erlang process there's no reason not to allow one to "assign" to existing variables. But at the time, "access control" and "immutable" were sort of conflated together. Rust is the first big language that seems to be levering those concepts apart in a really systematic way.)

However, the spectrum keeps going from here. Past Haskell there are functional languages that get really mathematical, and are focused on proving code, creating even more elaborate type systems such as dependent types ("this function takes a string whose length is a prime number", to give a silly example), and constraining the abstractions even further for things like total functional programming, which is one of the most interesting possible ways to limit programming so that it is not Turing Complete, but can still do real work. Here you can get such exotica as ways of using the type system to separate out what code is total, and what is not, in various principled ways. One of the common "I've been in this industry for 20 years and it sucks and here's what we all need to do to fix it" posts is to extol the virtues of one or more of these things. However, while there has been some interesting progress on many of these fronts in the past couple of decades, they remain impractical.