```python
from typing import List, Dict, Optional

def split_segments(path: str) -> List[str]:
    if not path or path[0] != '/':
        raise ValueError("Invalid path")
    segments = path.split('/')
    if segments[-1] == '':
        segments.pop()
    return segments

def first_route(patterns: List[str], path: str) -> int:
    for i, pattern in enumerate(patterns):
        if match_route(pattern, path) is not None:
            return i
    return -1

def match_route(pattern: str, path: str) -> Dict[str, str] | None:
    if not path or path[0] != '/':
        raise ValueError("Invalid path")
    if not pattern or pattern[0] != '/':
        raise ValueError("Invalid pattern")
    if path == pattern:
        return {}
    segments = split_segments(path)
    pattern_segments = split_segments(pattern)
    if len(segments) != len(pattern_segments):
        raise ValueError("Path and pattern must have the same number of segments")
    capture_names = set()
    for i, segment in enumerate(pattern_segments):
        if segment == '**':
            if '**' in pattern_segments:
                raise ValueError("Pattern cannot hold '**' more than once")
            for j in range(i + 1, len(segments)):
                if segments[j] != '':
                    raise ValueError("Pattern cannot hold '**' more than once")
            return {}
        elif segment == '*':
            if '*' in pattern_segments:
                raise ValueError("Pattern cannot hold '*' more than once")
            for j in range(i + 1, len(segments)):
                if segments[j] != '':
                    raise ValueError("Pattern cannot hold '*' more than once")
            return {}
        elif segment == ':':
            if i + 1 >= len(pattern_segments):
                raise ValueError("Pattern must have a name after a colon")
            if not pattern_segments[i + 1].isalnum():
                raise ValueError("Capture name must be a letter or underscore followed by letters, digits or underscores")
            capture_names.add(pattern_segments[i + 1])
        elif pattern_segments[i] != segments[i]:
            raise ValueError("Pattern and path must match exactly")
    if capture_names:
        return {name: segment for name, segment in zip(capture_names, segments)}
    return None
```