diff --git a/Documentation/API.md b/Documentation/API.md
index b66d9ce..374ceb7 100644
--- a/Documentation/API.md
+++ b/Documentation/API.md
@@ -1,5 +1,8 @@
# `API` — networking for LCEssentials
+Part of the reference set: [Extensions.md](Extensions.md) ·
+[SwiftUI.md](SwiftUI.md) · [UIKit.md](UIKit.md).
+
`API` is an `actor` that wraps `URLSession` for JSON REST calls and multipart
uploads. One line to send a typed request, decode the response, and get a
consistent error — instead of re-writing the same `URLRequest` / status-code /
diff --git a/Documentation/Extensions.md b/Documentation/Extensions.md
new file mode 100644
index 0000000..4a02881
--- /dev/null
+++ b/Documentation/Extensions.md
@@ -0,0 +1,2018 @@
+# LCEssentials — Extensions
+
+Foundation, value-type, string, collection, numeric, date, and crypto helpers, plus
+the `LCEssentials` namespace itself. UIKit extensions live in
+[UIKit.md](UIKit.md); SwiftUI helpers in [SwiftUI.md](SwiftUI.md).
+
+Every section is a collapsible block — click a heading to expand it.
+
+## Contents
+
+- [API & Networking](#api--networking)
+- [Strings & Text](#strings--text)
+- [Collections & Sequences](#collections--sequences)
+- [Numbers & Geometry](#numbers--geometry)
+- [Date, Data & Files](#date-data--files)
+- [Encoding & Errors](#encoding--errors)
+- [Crypto](#crypto)
+- [Core — the `LCEssentials` namespace](#core--the-lcessentials-namespace)
+
+---
+
+## API & Networking
+
+`API` is an `actor` wrapping `URLSession` for typed JSON requests and multipart
+uploads. This is a summary — the **[full guide is in API.md](API.md)** (all
+parameters, error model, client certificates, custom body types, and the
+rationale vs. a hand-rolled `URLSession`).
+
+
+API — typed async requests & uploads
+
+### `API.shared`
+
+The shared `actor` instance. Every call is `await`; nothing runs on the main
+thread unless you hop there yourself.
+
+```swift
+struct User: Decodable, Sendable { let id: Int; let name: String }
+
+let user: User = try await API.shared.request(
+ url: "https://api.example.com/users/{id}",
+ method: .get,
+ pathParams: ["id": "42"]
+)
+```
+
+### `request(url:method:body:pathParams:headers:debug:timeoutInterval:networkServiceType:persistConnection:)`
+
+Sends a request and decodes the JSON response into `T: Decodable & Sendable`.
+Non-2xx responses throw an `NSError` whose `code` is the HTTP status and whose
+`localizedFailureReason` is the response body. When `T == String` the raw body
+is returned without JSON decoding.
+
+```swift
+struct CreateUser: Encodable, Sendable { let name: String }
+
+// JSON body
+let created: User = try await API.shared.request(
+ url: "https://api.example.com/users",
+ method: .post,
+ body: jsonBody(CreateUser(name: "Ana"))
+)
+
+// form-url-encoded body
+let token: Token = try await API.shared.request(
+ url: "https://api.example.com/oauth/token",
+ method: .post,
+ body: .form(["grant_type": "password", "username": "ana"])
+)
+```
+
+### `upload(url:method:form:pathParams:headers:debug:timeoutInterval:networkServiceType:)` and its `onProgress:` overload
+
+Uploads a `multipart/form-data` body serialised to a temp file (removed
+afterwards) and streamed from disk, so large files never fully load into memory.
+
+```swift
+var form = MultipartForm()
+form.field("caption", "Sunset")
+form.file("photo", data: jpegData, filename: "p.jpg")
+form.file("video", url: localVideoURL) // streamed from disk
+
+let result: UploadResult = try await API.shared.upload(
+ url: "https://api.example.com/media",
+ form: form,
+ onProgress: { fraction in
+ Task { @MainActor in progressView.progress = Float(fraction) }
+ }
+)
+```
+
+### Body types — `HTTPBody`
+
+`jsonBody(_:)` wraps any `Encodable & Sendable`; `FormURLEncodedBody` (a.k.a.
+`.form([:])`) percent-escapes every value and never drops one; `RawBody` lets you
+supply the bytes and `Content-Type` yourself. Conform your own type to `HTTPBody`
+and `API` accepts it with no change.
+
+```swift
+struct CSVBody: HTTPBody {
+ let rows: [[String]]
+ func encoded() throws -> (data: Data, contentType: String) {
+ let text = rows.map { $0.joined(separator: ",") }.joined(separator: "\n")
+ return (Data(text.utf8), "text/csv; charset=UTF-8")
+ }
+}
+```
+
+### `await API.shared.setupCertification(certData:password:)`
+
+Registers a client certificate (`.p12`) for mutual-TLS on subsequent requests.
+
+```swift
+let p12 = try Data(contentsOf: certURL)
+await API.shared.setupCertification(certData: p12, password: "cert-pw")
+```
+
+
+
+## Strings & Text
+
+
+String — validation, parsing, formatting, masks, HTML, dates
+
+### URLs
+
+#### `var isValidUrl: Bool` / `var isValidHttpsUrl: Bool` / `var isValidHttpUrl: Bool`
+`isValidUrl` is true when `URL(string:)` succeeds; the other two additionally
+require the `https` / `http` scheme.
+
+```swift
+"https://google.com".isValidUrl // true
+"https://google.com".isValidHttpsUrl // true
+"http://google.com".isValidHttpsUrl // false
+```
+
+#### `var urlEncoded: String` / `var urlDecoded: String`
+Percent-encode (host-allowed set) / decode. `urlDecoded` returns the original
+string when it is not encoded.
+
+```swift
+"it's easy".urlEncoded // "it's%20easy"
+"it's%20easy".urlDecoded // "it's easy"
+```
+
+#### `mutating func urlEncode() -> String` / `mutating func urlDecode() -> String`
+In-place variants; also return the new value (`@discardableResult`).
+
+```swift
+var s = "a b"; s.urlEncode() // s == "a%20b"
+```
+
+#### `func stringByAddingPercentEncodingForRFC3986() -> String`
+Percent-encodes for use as a query key/value, escaping `;/?:@&=+$,` and space.
+
+```swift
+"a&b=c".stringByAddingPercentEncodingForRFC3986() // "a%26b%3Dc"
+```
+
+#### `var url: String?`
+First URL detected **inside** the string (via `NSDataDetector`), or `nil`.
+
+```swift
+"visit www.site.com.br now".url // "www.site.com.br"
+```
+
+#### `var toURL: NSURL?`
+`NSURL(string:)` wrapper.
+
+```swift
+"https://x.com".toURL // NSURL
+```
+
+### Validation
+
+#### `var isEmail: Bool`
+Regex check for a syntactically valid email (TLD 2–20 chars).
+
+```swift
+"user@example.com".isEmail // true
+"nope".isEmail // false
+```
+
+#### `var isCPF: Bool`
+Validates a Brazilian CPF including both check digits. Strips formatting first
+(`onlyNumbers`), requires 11 digits.
+
+```swift
+"123.456.789-09".isCPF // true when the check digits match
+```
+
+#### `var isValidCNPJ: Bool`
+Validates a Brazilian CNPJ (14 digits, both check digits, rejects all-same-digit).
+
+```swift
+"11.222.333/0001-81".isValidCNPJ // true when valid
+```
+
+#### `var isAlphabetic: Bool`
+Letters only, no digits.
+
+```swift
+"abc".isAlphabetic // true
+"123abc".isAlphabetic // false
+```
+
+#### `var isAlphaNumeric: Bool`
+Contains at least one letter **and** one digit and nothing else — handy for
+password rules.
+
+```swift
+"123abc".isAlphaNumeric // true
+"abc".isAlphaNumeric // false
+```
+
+#### `var isHTML: Bool`
+True if the string contains an HTML tag.
+
+```swift
+"hi".isHTML // true
+```
+
+#### `func validateBolean(comparingBoolean: Bool = true) -> Bool`
+Loose truthy/falsy check against a large set of EN/PT words. With
+`comparingBoolean: true` returns whether the string means "true"
+(`YES`, `ON`, `SIM`, `ATIVO`, `1`, `T`, …); with `false`, whether it means "false".
+
+```swift
+"SIM".validateBolean() // true
+"nao".validateBolean(comparingBoolean: false) // true
+```
+
+### Conversion
+
+#### `var bool: Bool?`
+`"true"/"yes"/"1"` → `true`, `"false"/"no"/"0"` → `false`, else `nil` (trimmed, case-insensitive).
+
+```swift
+" YES ".bool // true
+"maybe".bool // nil
+```
+
+#### `var int: Int?` / `var float: Float?` / `var double: Double?`
+Plain `Int(self)` / `Float(self)` / `Double(self)`.
+
+```swift
+"101".int // 101
+"1.5".double // 1.5
+"x".int // nil
+```
+
+#### `func float(locale: Locale = .current) -> Float?` / `func double(locale:) -> Double?`
+Locale-aware parsing via `NumberFormatter` (accepts grouping separators).
+
+```swift
+"1,5".double(locale: Locale(identifier: "pt_BR")) // 1.5
+```
+
+#### `var currencyStringToDouble: Double`
+Parses a `pt_BR` currency string to `Double`, `0.0` on failure.
+
+```swift
+"R$ 1.234,56".currencyStringToDouble // 1234.56
+```
+
+#### `var btcToSats: Int` / `var bitcoinToSatoshis: Int`
+Multiplies a BTC amount string by 100,000,000. `bitcoinToSatoshis` is an alias.
+
+```swift
+"0.0001".btcToSats // 10000
+```
+
+#### `var data: Data`
+UTF-8 bytes.
+
+```swift
+"hi".data // 2 bytes
+```
+
+#### `var nsString: NSString` / `var fullNSRange: NSRange`
+Bridge to `NSString`; `NSRange` spanning the whole string (UTF-16 aware).
+
+```swift
+"café".fullNSRange // {0, 4}
+```
+
+#### `func nsRange(from range: Range) -> NSRange?`
+Convert a Swift `Range` to an `NSRange` in the UTF-16 view.
+
+#### `var base64Encode: String?` / `var base64Decode: String?`
+Base64 encode the UTF-8 bytes / decode a Base64 string back to text.
+
+```swift
+"hi".base64Encode // "aGk="
+"aGk=".base64Decode // "hi"
+```
+
+#### `func date(withCurrFormatt:localeIdentifier:timeZone:) -> Date?`
+Parse the string to `Date` using the given input format (default
+`"yyyy-MM-dd HH:mm:ss"`, locale `pt-BR`, current time zone). A `" 0000"` suffix
+is normalised to `" +0000"`.
+
+```swift
+"2026-08-29 14:30:00".date() // Date
+```
+
+#### `func date(withCurrFormatt:newFormatt:localeIdentifier:timeZone:) -> Date?`
+Parse with one format and round-trip through another (normalises the value).
+
+#### `var currentTimeZone: String`
+Current time-zone offset string, e.g. `"-0300"`.
+
+### Cleaning & filtering
+
+#### `var withoutSpacesAndNewLines: String`
+Removes every space and `\n`.
+
+```swift
+" a \n b ".withoutSpacesAndNewLines // "ab"
+```
+
+#### `var onlyNumbers: String` / `var numbers: String`
+Digits only. `onlyNumbers` uses a `\D` regex; `numbers` uses `decimalDigits`.
+
+```swift
+"(11) 98765-4321".onlyNumbers // "11987654321"
+```
+
+#### `var letters: String` / `var lettersWithWhiteSpace: String`
+Keep only letters (optionally keeping spaces).
+
+```swift
+"a1 b2".letters // "ab"
+"a1 b2".lettersWithWhiteSpace // "a b"
+```
+
+#### `var alphanumeric: String` / `var alphanumericWithWhiteSpace: String`
+Keep only alphanumerics (optionally keeping spaces).
+
+```swift
+"a-b_c 1".alphanumeric // "abc1"
+```
+
+#### `var removeSpecialChars: String`
+Keeps `[A-Za-z0-9 -]` only.
+
+```swift
+"a@b#c".removeSpecialChars // "abc"
+```
+
+#### `var removeHTMLTags: String` / `var removeEmoji: String`
+Strip HTML tags / strip emoji (`CharacterSet.symbols`).
+
+```swift
+"hi
".removeHTMLTags // "hi"
+"hi 😀".removeEmoji // "hi "
+```
+
+### Slicing & padding
+
+#### `var first: String` / `var last: String`
+First / last character **as a String** (`""` when empty).
+
+#### `var uppercaseFirst: String`
+Capitalises the first character only.
+
+```swift
+"hello".uppercaseFirst // "Hello"
+```
+
+#### `var firstCharacterAsString: String?` / `var lastCharacterAsString: String?`
+Optional variants — `nil` when empty.
+
+#### `func paddingStart(_ length: Int, with: String = " ") -> String` / `func paddingEnd(...)`
+Pad to `length` with a repeating pad string at the start / end. No-op if already long enough.
+
+```swift
+"hue".paddingStart(10) // " hue"
+"hue".paddingEnd(10, with: "br") // "huebrbrbrb"
+```
+
+#### `func truncated(toLength: Int, trailing: String? = "...") -> String`
+Non-mutating truncation.
+
+```swift
+"This is long".truncated(toLength: 7) // "This is..."
+```
+
+#### `mutating func truncate(toLength: Int, trailing: String? = "...") -> String`
+In-place truncation (`@discardableResult`).
+
+#### `mutating func trim() -> String`
+Trim leading/trailing whitespace and newlines, in place (`@discardableResult`).
+
+```swift
+var s = " hi \n"; s.trim() // s == "hi"
+```
+
+#### `mutating func reverse() -> String`
+Reverse in place (`@discardableResult`).
+
+#### `mutating func insertAtIndexEnd(string:ind:)` / `insertAtIndexStart(string:ind:)`
+Insert `string` at an offset measured from `endIndex` (negative `ind` moves left).
+
+```swift
+var s = "abcd"; s.insertAtIndexEnd(string: "-", ind: -1) // "abc-d"
+```
+
+### Replacing
+
+#### `func replace(from:to:)` / `func findAndReplace(from:to:)`
+Simple substring replacement (`findAndReplace` is generic over `StringProtocol`).
+
+```swift
+"a.b.c".replace(from: ".", to: "-") // "a-b-c"
+```
+
+#### `func replacing(range: CountableClosedRange, with: String) -> String`
+Replace by integer character range.
+
+```swift
+"abcdef".replacing(range: 1...3, with: "X") // "aXef"
+```
+
+#### `func replacingLastOccurrenceOfString(_:with:caseInsensitive: Bool = true) -> String`
+Replace only the last match.
+
+```swift
+"a-b-c".replacingLastOccurrenceOfString("-", with: "+") // "a-b+c"
+```
+
+#### `func replaceAll(of pattern: String, with: String, options: = []) -> String`
+Regex replace-all; returns the original on a bad pattern.
+
+```swift
+"a1b2c3".replaceAll(of: "[0-9]", with: "#") // "a#b#c#"
+```
+
+#### `@discardableResult func replaceURL(_ withDict: [String: Any]) -> String`
+Substitute `{key}` placeholders — used by `API.request(pathParams:)`.
+
+```swift
+"/users/{id}/posts/{p}".replaceURL(["id": 7, "p": "x"]) // "/users/7/posts/x"
+```
+
+### Words & search
+
+#### `func words() -> [String]` / `func wordCount() -> Int`
+Split on whitespace + punctuation, dropping empties.
+
+```swift
+"Swift is amazing".words() // ["Swift", "is", "amazing"]
+"Swift is amazing".wordCount() // 3
+```
+
+#### `func contains(_:caseSensitive: Bool = true) -> Bool`
+Substring check with optional case-insensitivity.
+
+```swift
+"Hello".contains("ell") // true
+"Hello".contains("HELLO", caseSensitive: false) // true
+```
+
+### Formatting helpers
+
+#### `func applyMask(toText: String, mask: String) -> String`
+Apply a `#`-placeholder mask; literal characters in the mask are inserted.
+
+```swift
+"11987654321".applyMask(toText: "11987654321", mask: "(##) #####-####")
+// "(11) 98765-4321"
+```
+
+#### `func exponentize(str: String) -> String`
+Turn `^`-prefixed digits into Unicode superscripts.
+
+```swift
+"x^2 + y^3".exponentize(str: "x^2 + y^3") // "x² + y³"
+```
+
+#### `func stringFromTimeInterval(_ interval: TimeInterval) -> NSString`
+Format a `TimeInterval` as `HH:MM:SS.mmm`.
+
+```swift
+"".stringFromTimeInterval(3661.5) // "01:01:01.500"
+```
+
+#### `func toSlug() -> String`
+Lowercase, de-accent, spaces → `-`, strip other punctuation.
+
+```swift
+"Olá Mundo!".toSlug() // "ola-mundo"
+```
+
+#### `func localized(comment: String = "") -> String`
+`NSLocalizedString(self, comment:)`.
+
+```swift
+"welcome_title".localized()
+```
+
+### Generators & misc
+
+#### `static func loremIpsum(ofLength length: Int = 445) -> String`
+Lorem-ipsum text truncated to `length` (max 445).
+
+```swift
+String.loremIpsum(ofLength: 20) // "Lorem ipsum dolor si"
+```
+
+#### `func randomString(length: Int) -> String`
+Random `[A-Za-z0-9]` string. (Instance method — the receiver is ignored.)
+
+```swift
+"".randomString(length: 8) // e.g. "a9Fk2Lp0"
+```
+
+#### `var JSONStringToDictionary: [String: Any]?`
+Parse a JSON object string to a dictionary (`nil` + logs on failure).
+
+```swift
+#"{"a":1}"#.JSONStringToDictionary // ["a": 1]
+```
+
+#### `var convertToHTML: NSAttributedString?`
+Render an HTML string to `NSAttributedString` (UIKit path uses the CSS converter below).
+
+#### `func convertHtmlToAttributedStringWithCSS(font:csscolor:lineheight:csstextalign:customCSS:) -> NSAttributedString?` — *UIKit only*
+HTML → `NSAttributedString` with an injected `