# SuhuFramework — Complete Guide

> **v1.0.0** · **SuhuFramework UI** (visual) · **SuhuFramework class names** (API only) · Zero config · Light & Dark mode

---

## Table of Contents

1. [Getting Started](#getting-started)
2. [Dark / Light Mode](#dark--light-mode)
3. [Navbar & Offcanvas](#navbar--offcanvas)
4. [Grid System](#grid-system)
5. [Typography](#typography)
6. [Buttons](#buttons)
7. [Cards](#cards)
8. [Alerts](#alerts)
9. [Badges & Chips](#badges--chips)
10. [Forms](#forms)
11. [Modals](#modals)
12. [Accordion](#accordion)
13. [Tabs](#tabs)
14. [Progress](#progress)
15. [Spinners](#spinners)
16. [Tables](#tables)
17. [Breadcrumb](#breadcrumb)
18. [Pagination](#pagination)
19. [Dropdowns](#dropdowns)
20. [Utilities](#utilities)
21. [Animations](#animations)
22. [Collapse](#collapse)
23. [List Group](#list-group)
24. [Scrollspy](#scrollspy)
25. [Input Group](#input-group)
26. [Floating Labels](#floating-labels)
27. [Validation](#validation)
28. [JavaScript API](#javascript-api)
29. [CSS Variables](#css-variables)

---

## Getting Started

### 1. Include the files

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="My app built with SuhuFramework">
  <meta name="color-scheme" content="light dark">
  <!-- Optional: paste snippets/theme-init.html before CSS to avoid theme flash -->
  <title>My App</title>
  <link rel="stylesheet" href="suhu-framework.min.css">
</head>
<body>
  <header>
    <nav aria-label="Main navigation"><!-- navbar --></nav>
  </header>
  <main>
    <!-- Your content -->
  </main>
  <footer><!-- optional --></footer>
  <script src="suhu-framework.min.js" defer></script>
</body>
</html>
```

Use HTML5 semantics (`header`, `main`, `nav`, `section`, `footer`), one `<h1>` per page, explicit `type="button"` on non-submit buttons, and associated `<label for="…">` on form fields.

For local development you may use `suhu-framework.css` / `suhu-framework.js`; production sites should ship the **`.min`** builds (regenerate with `./scripts/build-min.sh` after editing source).

### 2. Design philosophy

- **Class names** — Standard SuhuFramework API (`.btn`, `.card`, `.modal`, `.form-control`, …) for predictable, familiar markup.
- **Visual UI** — SuhuFramework design system: slate palette, `rounded-md`, default **`shadow-md`** on buttons, `shadow-sm` on form controls, ring borders, `focus-visible` rings, no hover “lift” on cards/buttons.

All styles live in **`suhu-framework.css`** (source) or **`suhu-framework.min.css`** (production). JavaScript: **`suhu-framework.js`** / **`suhu-framework.min.js`**.

### 3. Auto-initialization

`SuhuFramework.init()` runs automatically on `DOMContentLoaded`. No manual setup needed.

### 4. Manual initialization

```js
// Available globally as SuhuFramework (Suhu is a backward-compatible alias)
SuhuFramework.init();           // Re-initialize all components
SuhuFramework.openModal(el);    // Open a modal
SuhuFramework.closeModal(el);   // Close a modal
```

### 5. Component rounding (`.rounded`)

SuhuFramework uses **`.btn`** with a default **pill** shape. Other components use the **`.rounded`** utility (`border-radius: var(--radius)`) on their roots instead of baked-in radii:

| Utility | Use on |
|---------|--------|
| *(default)* | **`.btn`** — pill shape (`border-radius: full`); no `.rounded` class needed |
| *(default)* | **`.modal`** — `rounded-4` (`--radius-xl` / 12px); no `.rounded` class needed |
| *(default)* | **`.alert`** — `rounded-3` (`--radius-lg` / 8px); no `.rounded` class needed |
| *(default)* | **`.accordion`**, **`.toast`**, **`.table-responsive`**, **`.table-datatable`**, **`.collapse`** (not `.accordion-collapse`), **`.list-group`** — `rounded-3`; no `.rounded` class needed |
| `.rounded` | `card-body`, `modal-close`, `form-control`, `form-select`, `dropdown-menu`, `dropdown-item`, `submenu`, `toast-close`, `carousel`, `feature-card`, `stat-card`, `page-link`, `input-group-text`, `breadcrumb` |
| `.rounded-1` … `.rounded-5` | Same surfaces as `.rounded` (e.g. `card-body rounded-3` for larger card corners) |
| `.rounded-pill` | `badge`, `chip`, `progress` |
| `.btn.rounded-0` / `.btn.rounded-circle` | Square or circle buttons (overrides default pill) |

Exceptions (keep built-in shape): checkbox, radio, switch, spinner, `nav-pills` links, carousel controls, `list-group-flush`, `modal-fullscreen`, `btn-icon`.

```html
<button class="btn btn-primary">Save</button>
<div class="alert alert-success">Saved.</div>
```

---

## Theme (Light / Dark / Auto)

SuhuFramework supports three color scheme modes:

| Mode | Storage (`sf-theme-mode`) | Behavior |
|---|---|---|
| Light | `light` | Always light UI |
| Dark | `dark` | Always dark UI |
| Auto | `auto` | Follows OS `prefers-color-scheme` (default on first visit) |

### How it works

- User preference: `data-theme-mode` on `<html>` (`light` | `dark` | `auto`)
- Resolved theme for CSS: `data-theme` on `<html>` (`light` | `dark`)

### Theme picker (recommended)

```html
<div class="sf-theme-picker" role="group" aria-label="Color scheme">
  <button type="button" class="sf-theme-picker__btn" data-theme-mode-option="light">☀ Light</button>
  <button type="button" class="sf-theme-picker__btn" data-theme-mode-option="dark">☾ Dark</button>
  <button type="button" class="sf-theme-picker__btn" data-theme-mode-option="auto">◐ Auto</button>
</div>
```

### Cycle button (optional)

```html
<button type="button" data-theme-toggle>
  <span data-theme-icon>◐</span>
  <span data-theme-label>Auto</span>
</button>
```

Cycles: Light → Dark → Auto. Icon/label update automatically.

### JavaScript API

```js
SuhuFramework.setThemeMode('auto');  // 'light' | 'dark' | 'auto'
SuhuFramework.getThemeMode();         // stored preference
```

## Direction (LTR / RTL)

Set document reading direction on `<html>`:

```html
<div class="sf-dir-picker" role="group" aria-label="Text direction">
  <button type="button" class="sf-dir-picker__btn" data-dir-option="ltr">LTR</button>
  <button type="button" class="sf-dir-picker__btn" data-dir-option="rtl">RTL</button>
</div>
```

```js
SuhuFramework.setDirection('rtl');  // 'ltr' | 'rtl'
SuhuFramework.getDirection();
```

Preference is stored in `localStorage` as `sf-dir`. Spacing utilities `.ms-*`, `.me-*`, `.ps-*`, `.pe-*` and `.text-start` / `.text-end` use logical properties. Offcanvas, dropdowns, and list-group active states include RTL overrides.

## Popovers

Click-triggered overlays with title and body (dismiss on outside click or Escape):

```html
<button type="button"
        class="btn btn-primary"
        data-bs-toggle="popover"
        data-bs-title="Title"
        data-bs-content="Body text here."
        data-bs-placement="top">
  Toggle popover
</button>
```

| Attribute | Description |
|---|---|
| `data-bs-toggle="popover"` | Enables popover on click |
| `data-bs-title` | Header text |
| `data-bs-content` | Body text |
| `data-bs-placement` | `top`, `bottom`, `left`, `right` |
| `data-bs-html="true"` | Optional: allow HTML in title/content |

```js
SuhuFramework.closePopover();
```

### CSS semantic tokens

All components use semantic CSS custom properties that respond to the active theme:

```css
/* Page */
--sf-bg             /* body background */
--sf-bg-subtle      /* section backgrounds */
--sf-surface        /* card / panel background */
--sf-surface-2      /* elevated surface */
--sf-border         /* border color */

/* Text */
--sf-color-text         /* primary text */
--sf-color-text-muted   /* secondary text */
--sf-color-text-subtle  /* disabled / hint text */

/* Form elements */
--sf-input-bg       /* input background */
--sf-input-border   /* input border */
--sf-input-text     /* input text color */
```

Use these variables in your own components to automatically support both themes:

```css
.my-card {
  background: var(--sf-surface);
  border: 1px solid var(--sf-border);
  color: var(--sf-color-text);
}
```

---

## Navbar & Offcanvas

On viewports **≥992px**, the navbar shows `.navbar-nav` and `.navbar-actions`. Below that breakpoint, CSS **hides** those blocks — the hamburger must open an **Offcanvas** panel. A toggler alone is not enough.

See `index.html` — sections **Navbar** (`#navbar-section`) and **Offcanvas** (`#offcanvas`), or copy `includes/snippets/navbar-with-offcanvas.html`.

### Complete example (copy-paste)

```html
<nav class="navbar" aria-label="Main navigation">
  <div class="container" style="display:flex;align-items:center;height:100%;min-width:0;">
    <a href="#" class="navbar-brand">
      <img src="assets/img/logo.svg" alt="" class="brand-logo" width="32" height="32" decoding="async">
      Brand
    </a>

    <!-- Desktop only -->
    <ul class="navbar-nav">
      <li class="nav-item"><a href="#" class="nav-link">Home</a></li>
      <li class="nav-item">
        <a href="#" class="nav-link" aria-haspopup="true">Products</a>
        <div class="dropdown-menu rounded-3">
          <a href="#" class="dropdown-item rounded">Item</a>
        </div>
      </li>
    </ul>

    <div class="navbar-actions">
      <a href="#" class="btn btn-primary">Get Started</a>
    </div>

    <!-- Mobile/tablet — MUST link to offcanvas id -->
    <button type="button" class="navbar-toggler rounded"
            data-bs-toggle="offcanvas"
            data-bs-target="#myNavOffcanvas"
            aria-controls="myNavOffcanvas"
            aria-expanded="false"
            aria-label="Open menu">
      <span class="bar"></span><span class="bar"></span><span class="bar"></span>
    </button>
  </div>
</nav>

<div id="myNavOffcanvas" class="offcanvas offcanvas-start"
     role="dialog" aria-modal="true" aria-label="Navigation menu" aria-hidden="true">
  <div class="offcanvas-header">
    <a href="#" class="navbar-brand">Brand</a>
    <button type="button" class="offcanvas-close" aria-label="Close menu">✕</button>
  </div>
  <div class="offcanvas-body">
    <a href="#" class="offcanvas-nav-link">
      <span class="link-icon">🏠</span>
      <span class="link-text">Home</span>
    </a>
    <div class="offcanvas-nav-item">
      <button type="button" class="offcanvas-nav-link" data-toggle>
        <span class="link-text">More</span>
      </button>
      <div class="offcanvas-submenu">
        <a href="#" class="offcanvas-submenu-link">Sub page</a>
      </div>
    </div>
  </div>
</div>

<div class="navbar-spacer" aria-hidden="true"></div>
```

### Desktop navbar

- Fixed top, blur backdrop, scroll shadow (`.navbar.scrolled`)
- **Menu position (desktop only)** on `<ul class="navbar-nav">`:
  - **`.navbar-nav-left`** — links after the logo (default; plain `.navbar-nav` behaves the same)
  - **`.navbar-nav-right`** — links aligned to the right, before `.navbar-actions`
- Dropdowns: hover on `.nav-item` with child `.dropdown-menu`
- Nested flyout: `.dropdown-item.has-submenu` + `.submenu`

```html
<div class="container">
  <a href="#" class="navbar-brand">Brand</a>
  <ul class="navbar-nav navbar-nav-left">…</ul>
  <!-- or: <ul class="navbar-nav navbar-nav-right">…</ul> -->
  <div class="navbar-actions">…</div>
  <button type="button" class="navbar-toggler" data-bs-toggle="offcanvas" data-bs-target="#myNavOffcanvas">…</button>
</div>
```

### Light variant

```html
<nav class="navbar navbar-light">...</nav>
```

### Offcanvas component

| Class / attribute | Purpose |
|---|---|
| `.offcanvas.offcanvas-start` | Panel slides from left (also `-end`, `-top`, `-bottom`) |
| `data-bs-toggle="offcanvas"` | On trigger button |
| `data-bs-target="#id"` | Panel to open (must match panel `id`) |
| `.offcanvas-close` | Close button inside panel |
| `data-toggle` on `.offcanvas-nav-link` | Expand/collapse submenu |
| `.offcanvas-nested-menu` | Level-2 nested links |

**Behavior:** backdrop created by JS; click outside or `ESC` closes; `SuhuFramework.openOffcanvas('myNavOffcanvas')` / `closeOffcanvas(id)`.

Legacy `.drawer` / `.drawer-overlay` class names may still work for older markup; new projects should use `.offcanvas`.

---

## Carousel

Markup (Swiper-like):

```html
<div class="carousel myCarousel">
  <div class="carousel-wrapper">
    <div class="carousel-slide">
      <img src="slide-1.jpg" alt="" />
    </div>
    <div class="carousel-slide">
      <img src="slide-2.jpg" alt="" />
    </div>
  </div>
</div>
```

Legacy classes `.carousel-inner` / `.carousel-item` still work. Optional: prev/next (`.carousel-control-prev/next`), dots (`.carousel-indicator`), captions (`.carousel-caption`).

```javascript
const sfcarousel = new SFCarousel('.myCarousel', {
  spaceBetween: 20,
  slidesPerView: 1,
  gridRows: 1,
  loop: true,
  autoplay: {
    delay: 2500,
    disableOnInteraction: false,
  },
  zoom: 'doubleClick',
  thumbnail: true,
  navigation: true,
  pagination: true,
});
```

| Option | Description |
|--------|-------------|
| `spaceBetween` | Gap between slides (px) |
| `slidesPerView` | Visible slides (`1.5` = peek) |
| `gridRows` | `1` = row; `2+` = multi-row grid |
| `loop` | Wrap at ends (`true` default) |
| `autoplay` | `false` or `{ delay, disableOnInteraction }` |
| `zoom` | `false`, `'singleClick'`, `'doubleClick'`. Overlay: open/close fade+scale; between images uses zoom-out/zoom-in (buttons, ←/→, touch swipe) |
| `thumbnail` | `false`, `true` (auto from `.carousel-slide > img`), or `'#selector'`. Auto thumbs: max **7** desktop / **5** tablet (≥768px) / **3** mobile; full-width sizing, centered; horizontal swipe when count exceeds max |
| `navigation` | Prev/next arrows (`true` default, auto-injected) |
| `pagination` | Dot indicators (`true` default, auto-injected) |

**Instance methods:** `.goTo(n)`, `.next()`, `.prev()`, `.startAutoplay()`, `.stopAutoplay()`, `.destroy()`.

See `index.html` — **Carousel** (`#carousel`).

---

## Grid System

12-column responsive grid. Breakpoints: 576 / 768 / 992 / 1200 / 1400px.

```html
<!-- Full-bleed background on parent; content inset via .container -->
<section class="bg-gradient-primary">
  <div class="container">
    <div class="row">
      <div class="col-12 col-md-6">Col A</div>
      <div class="col-12 col-md-6">Col B</div>
    </div>
  </div>
</section>

<!-- Tighter edge padding (navbar, full-width bands) -->
<header class="navbar">
  <div class="container-fluid">…</div>
</header>
```

| Class | Description |
|-------|-------------|
| `.container` | Full width (`max-width: 100%`). Wider horizontal inset: `padding-left` / `padding-right` via `--sf-container-padding` (1rem → 3rem @768px → **7rem** @992px). Use for page content inside full-bleed sections. |
| `.container-fluid` | Full width with tighter inset: `--sf-gutter` (1rem → 1.5rem @768px). Use for navbars and edge-to-edge bands. |
| `.container-sm` … `.container-2xl` | Same padding as `.container-fluid` (legacy aliases; all `max-width: 100%`). |
| `.row` | Flex row wrapper |
| `.col` | Equal-width auto column |
| `.col-{1-12}` | Fixed-width column |
| `.col-sm-*`, `.col-md-*`, `.col-lg-*`, `.col-xl-*`, `.col-xxl-*` | Breakpoint columns |
| `.offset-{n}` | Column offset |
| `.g-{0-5}` | Gap between columns |
| `.px-container` | Utility: apply `--sf-container-padding` on any element |
| `.px-gutter` | Utility: apply `--sf-gutter` on any element |

---

## Typography

```html
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<div class="display-1">Display 1</div>
<div class="display-4 text-gradient">Gradient heading</div>
<p class="lead">Large intro paragraph</p>
<p class="text-muted">Muted text</p>

<!-- Gradient text variants -->
<span class="text-gradient">Blue → Purple → Cyan</span>
<span class="text-gradient-blue">Blue → Cyan</span>
<span class="text-gradient-purple">Purple → Pink</span>
<span class="text-gradient-cyan">Cyan → Emerald</span>

<!-- Label highlight pill -->
<span class="label-highlight">✨ New Feature</span>
```

---

## Masthead

Page header banner below a fixed navbar. Uses `.bg-gradient-primary` for the hero gradient and white title/lead text. See **`index.html` → Masthead** (`#masthead`).

```html
<nav class="navbar">…</nav>
<div class="navbar-spacer" aria-hidden="true"></div>
<header class="masthead bg-gradient-primary">
  <div class="masthead-content container">
    <h1 class="masthead-title">Page title</h1>
    <p class="masthead-lead">Intro copy.</p>
    <div class="masthead-actions">
      <a href="#" class="btn btn-primary">Action</a>
    </div>
  </div>
</header>
```

| Class | Purpose |
|-------|---------|
| `.masthead` | Root banner — vertical padding via `--sf-masthead-padding-top/bottom` (2.5rem→3rem top, 3rem bottom) |
| `.masthead-compact` | Shorter variant (`--sf-masthead-compact-padding-*`) |
| `.masthead-offset-nav` | Add navbar height to top padding when **not** using `.navbar-spacer` |
| `.masthead-content` | Inner wrapper (use with `.container`) |
| `.masthead-title` / `.masthead-lead` | Title and intro copy |
| `.masthead-actions` | Button row (flex + gap built-in) |

Do **not** add inline `padding` on `.masthead` — use tokens in `:root` or the compact/offset modifiers.

---

## Jumbrotton

Hero content block with the same gradient treatment as Masthead.

- `.jumbrotton` — single centered column
- `.jumbrotton.jumbrotton-2col` — text + `.jumbrotton-media` (stacks on mobile)
- `.jumbrotton-title` / `.jumbrotton-lead` / `.jumbrotton-actions`

See **`index.html` → Jumbrotton** (`#jumbrotton`).

---

## Buttons

All `.btn` elements use **`shadow-md`** by default (pill shape). Default size is medium — omit a size class or use **`.btn-md`** explicitly.

```html
<!-- Solid -->
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-warning">Warning</button>
<button class="btn btn-info">Info</button>
<button class="btn btn-light">Light</button>
<button class="btn btn-dark">Dark</button>

<!-- Outline -->
<button class="btn btn-outline-primary">Outline Primary</button>

<!-- Gradient -->
<button class="btn btn-gradient">Blue Gradient</button>
<button class="btn btn-gradient-purple">Purple Gradient</button>

<!-- Sizes (.btn = .btn-md default) -->
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-md">Medium</button>
<button class="btn btn-primary">Medium (default)</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary btn-xl">Extra Large</button>

<!-- Disabled -->
<button class="btn btn-primary" disabled>Disabled</button>

<!-- Block (full-width) -->
<button class="btn btn-primary btn-block">Full Width</button>

<!-- Icon button -->
<button class="btn btn-primary btn-icon">✨</button>
```

---

## Cards

Add `.rounded` to every `.card-body` (default radius: `var(--radius)`). Body-only cards draw border/shadow on the body; cards with header, footer, or image keep the outer shell radius.

```html
<!-- Basic -->
<div class="card">
  <div class="card-body rounded">
    <h5 class="card-title">Title</h5>
    <p class="card-text">Content</p>
    <a href="#" class="btn btn-primary btn-sm">Action</a>
  </div>
</div>

<!-- With header/footer -->
<div class="card">
  <div class="card-header">Header</div>
  <div class="card-body rounded">Body</div>
  <div class="card-footer">Footer</div>
</div>

<!-- Dark card -->
<div class="card card-dark">...</div>

<!-- Gradient border card -->
<div class="card card-gradient-border">...</div>

<!-- Feature card (for dark sections) -->
<div class="feature-card rounded">
  <div class="feature-icon">⚡</div>
  <h3 class="card-title">Feature</h3>
  <p class="card-text">Description</p>
</div>

<!-- Equal-width row (fills container) -->
<div class="card-group">
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card card-gradient-border">...</div>
</div>
```

`.card-group` stretches children to equal width and height (stacks on viewports under 768px).

---

## Alerts

```html
<div class="alert alert-primary">
  <span class="alert-icon">ℹ️</span>
  <div class="alert-content">
    <div class="alert-title">Title</div>
    Message text here.
  </div>
</div>

<!-- Dismissible -->
<div class="alert alert-danger alert-dismissible">
  <span class="alert-icon">🚨</span>
  <div class="alert-content">Error message.</div>
  <button class="alert-dismiss">✕</button>
</div>
```

**Variants:** `alert-primary`, `alert-secondary`, `alert-success`, `alert-danger`, `alert-warning`, `alert-info`, `alert-light`, `alert-dark`

Default corner radius is `rounded-3` (8px). Use `rounded-0` on an alert for square corners.

---

## Badges & Chips

```html
<!-- Badges -->
<span class="badge rounded-pill bg-primary">New</span>
<span class="badge rounded-pill bg-success">Active</span>
<span class="badge rounded-pill bg-blue-subtle">Info</span>
<span class="badge rounded-pill bg-green-subtle">Live</span>

<!-- Button with badge -->
<button class="btn btn-primary">
  Inbox <span class="badge rounded-pill bg-white text-dark ms-1">4</span>
</button>

<!-- Chips -->
<span class="chip rounded-pill">Default</span>
<span class="chip rounded-pill chip-primary">Primary</span>
<span class="chip rounded-pill chip-success">✓ Done</span>
<span class="chip rounded-pill chip-danger">✗ Failed</span>
```

---

## Forms

Form controls use the SuhuFramework design system: `rounded-md`, `shadow-sm`, `ring-1 ring-inset`, focus ring, 16px checkboxes, and styled toggle switches.

**Sizes:** `.form-control-sm` / default / `.form-control-lg` and `.form-select-sm` / default / `.form-select-lg`.

**Date picker** (built-in): `data-sf-date-picker`, optional `data-sf-date-locale="id|en"`, `data-sf-date-format="DD/MM/YYYY"`, `data-sf-date-min` / `data-sf-date-max` (ISO). API: `SFDatePicker.init()`, `SFDatePicker.create(input, options)`.

```html
<div class="sf-form-field">
  <label class="form-label" for="email">Email <span class="required">*</span></label>
  <input type="email" class="form-control rounded" id="email" placeholder="you@example.com">
</div>

<div class="sf-form-field">
  <label class="form-label" for="pass">Password</label>
  <input type="password" class="form-control rounded is-valid" id="pass">
  <div class="valid-feedback">✓ Strong password</div>
</div>

<div class="sf-form-field">
  <label class="form-label" for="user">Username</label>
  <input type="text" class="form-control rounded is-invalid" id="user">
  <div class="invalid-feedback">This username is already taken.</div>
</div>

<select class="form-select rounded">...</select>
<textarea class="form-control rounded" rows="3"></textarea>

<!-- Control sizes -->
<input type="text" class="form-control form-control-sm rounded" placeholder="Small">
<input type="text" class="form-control rounded" placeholder="Default">
<input type="text" class="form-control form-control-lg rounded" placeholder="Large">
<select class="form-select form-select-sm rounded">...</select>
<select class="form-select form-select-lg rounded">...</select>

<div class="form-check">
  <input class="form-check-input" type="checkbox" id="c1">
  <label class="form-check-label" for="c1">Accept terms</label>
</div>

<div class="form-check">
  <input class="form-check-input" type="radio" name="plan" id="r1">
  <label class="form-check-label" for="r1">Monthly plan</label>
</div>

<div class="form-check form-switch">
  <input class="form-check-input" type="checkbox" id="sw1">
  <label class="form-check-label" for="sw1">Enable notifications</label>
</div>

<label class="form-label">Volume: <span id="rangeVal">50</span>%</label>
<input type="range" class="form-range w-100" min="0" max="100" value="50"
       oninput="document.getElementById('rangeVal').textContent=this.value">

<!-- Input group (separate pattern) -->
<div class="input-group">
  <span class="input-group-text rounded">@</span>
  <input type="text" class="form-control rounded" placeholder="username">
</div>
```

---

## Modals

```html
<!-- Trigger -->
<button data-modal-target="#myModal">Open Modal</button>

<!-- Modal markup -->
<div class="modal-overlay" id="myModal">
  <div class="modal">
    <div class="modal-header">
      <h5 class="modal-title">Title</h5>
      <button class="modal-close rounded" data-modal-close>✕</button>
    </div>
    <div class="modal-body">
      Content here.
    </div>
    <div class="modal-footer">
      <button class="btn btn-ghost" data-modal-close>Cancel</button>
      <button class="btn btn-primary">Confirm</button>
    </div>
  </div>
</div>
```

**Sizes:** `.modal-sm` (24rem), `.modal` (36rem default), `.modal-lg` (52rem), `.modal-xl` (68rem), `.modal-fullscreen`

Dialogs use **`.modal`** with default **`rounded-4`** (`border-radius: var(--radius-xl)`). Use `.modal.rounded-0` or `.modal-fullscreen` for square corners.

### Backdrop behavior

| Behavior | How to enable |
|----------|----------------|
| **Default** — click outside closes | (no attribute) |
| **Static backdrop** — outside click ignored | `data-modal-backdrop="static"` or `data-bs-backdrop="static"` on `.modal-overlay`, or class `modal-static-backdrop` |
| **Disable ESC** | `data-modal-keyboard="false"` or `data-bs-keyboard="false"` on `.modal-overlay` |

Static backdrop shows a brief shake on the dialog when the user clicks the dimmed overlay. Close with ✕, footer buttons, or ESC (unless keyboard is disabled).

### Form layout inside modals

Use **`.modal-form-grid`** for stacked fields; add **`.modal-form-grid--2col`** from 768px up. Span full width with **`.modal-form-grid-span`**. Avoid raw `.row` / `.col-*` inside `.modal-body` (page grid gutters misalign with header/footer).

```html
<div class="modal-form-grid modal-form-grid--2col">
  <div><label class="form-label">First Name</label><input class="form-control rounded"></div>
  <div><label class="form-label">Last Name</label><input class="form-control rounded"></div>
  <div class="modal-form-grid-span">
    <label class="form-label">Email</label>
    <input type="email" class="form-control rounded">
  </div>
</div>
```

```html
<div class="modal-overlay" id="confirmModal" data-modal-backdrop="static">
  <div class="modal">
    <div class="modal-body">
      <p>Outside clicks will not dismiss this dialog.</p>
    </div>
    <div class="modal-footer">
      <button class="btn btn-primary" data-modal-close>OK</button>
    </div>
  </div>
</div>
```

**JS API:**
```js
SuhuFramework.openModal(document.querySelector('#myModal'));
SuhuFramework.closeModal(document.querySelector('#myModal'));
```

---

## Accordion

```html
<div class="accordion">
  <div class="accordion-item">
    <div class="accordion-header">
      <button class="accordion-button active">
        Question title
        <svg class="accordion-icon">...</svg>
      </button>
    </div>
    <div class="accordion-collapse show">
      <div class="accordion-body">Answer content.</div>
    </div>
  </div>

  <div class="accordion-item">
    <div class="accordion-header">
      <button class="accordion-button">Another question</button>
    </div>
    <div class="accordion-collapse">
      <div class="accordion-body">Another answer.</div>
    </div>
  </div>
</div>

<!-- Dark accordion -->
<div class="accordion accordion-dark">...</div>

<!-- Multi-open (allow multiple panels open) -->
<div class="accordion" data-multi-open>...</div>
```

---

## Tabs

```html
<!-- Underline tabs -->
<ul class="nav-tabs">
  <li><a href="#t1" class="nav-link active" data-target="#t1">Tab 1</a></li>
  <li><a href="#t2" class="nav-link" data-target="#t2">Tab 2</a></li>
</ul>
<div class="tab-content">
  <div class="tab-pane active" id="t1">Content 1</div>
  <div class="tab-pane" id="t2">Content 2</div>
</div>

<!-- Pill tabs -->
<div class="nav-pills">
  <a class="nav-link active" href="#p1" data-target="#p1">All</a>
  <a class="nav-link" href="#p2" data-target="#p2">Active</a>
</div>
```

### Tab alignment

`.nav-tabs` and `.nav-pills` are flex rows (`width: 100%`). Use flex utilities or fill classes:

| Alignment | Classes |
|-----------|---------|
| Start (default) | `justify-content-start` or omit |
| Center | `justify-content-center` |
| End | `justify-content-end` |
| Justify (equal width) | `nav-fill` or `nav-justified` |

```html
<ul class="nav-tabs justify-content-center">...</ul>
<ul class="nav-tabs nav-fill">...</ul>
<div class="nav-pills justify-content-end">...</div>
```

---

## Progress

```html
<!-- Basic -->
<div class="progress rounded-pill">
  <div class="progress-bar" data-width="75%"></div>
</div>

<!-- Colored variants -->
<div class="progress rounded-pill">
  <div class="progress-bar progress-bar-success" data-width="90%"></div>
</div>
<div class="progress rounded-pill">
  <div class="progress-bar progress-bar-danger" data-width="30%"></div>
</div>
<div class="progress rounded-pill">
  <div class="progress-bar progress-bar-warning" data-width="55%"></div>
</div>
<div class="progress rounded-pill">
  <div class="progress-bar progress-bar-info" data-width="40%"></div>
</div>

<!-- Sizes -->
<div class="progress rounded-pill progress-sm">...</div>
<div class="progress rounded-pill progress-lg">...</div>
<div class="progress rounded-pill progress-xl">...</div>
```

The `data-width` attribute enables scroll-triggered animation via IntersectionObserver.

---

## Spinners

```html
<div class="spinner-border spinner-primary"></div>
<div class="spinner-border spinner-success"></div>
<div class="spinner-border spinner-danger"></div>

<!-- Grow variant -->
<div class="spinner-grow spinner-primary"></div>

<!-- Sizes -->
<div class="spinner-border spinner-sm spinner-primary"></div>
<div class="spinner-border spinner-lg spinner-primary"></div>

<!-- Inside button -->
<button class="btn btn-primary" disabled>
  <div class="spinner-border spinner-sm"></div>
  Loading...
</button>
```

---

## Tables

### Basic table

```html
<div class="table-responsive">
  <table class="table rounded table-striped table-hover">
    <thead>
      <tr>
        <th>#</th>
        <th>Name</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>John Doe</td>
        <td><span class="badge rounded-pill bg-green-subtle">Active</span></td>
      </tr>
    </tbody>
  </table>
</div>
```

**Modifiers:** `.table-striped`, `.table-hover`, `.table-bordered`, `.table-dark`

### Search, count & pagination (optional)

Wrap the responsive table in `.table-datatable` and enable only the features you need via data attributes. Toolbar (search + export/refresh actions) and footer (count + pagination) are injected automatically by `SuhuFramework.init()`. Export loads [SheetJS](https://cdn.sheetjs.com/) from jsDelivr on first click.

**Mobile-first toolbar:** On viewports under 640px, search spans full width; Refresh / Export buttons sit in an equal-width grid below (touch-friendly `min-height: 2.5rem`). Footer stacks count text above centered pagination. From `640px` up, layout matches desktop (search left, actions right).

| Attribute | Description |
|-----------|-------------|
| `data-table-search` | Live filter across row text |
| `data-table-pagination="4"` | Rows per page (`"10"` default if value omitted; `"false"` disables) |
| `data-table-count` | Info line under the table |
| `data-table-search-placeholder` | Search input placeholder |
| `data-table-columns="1,2,3"` | Column indexes to search (0-based); default = all |
| `data-table-count-template` | Count text with `{start}`, `{end}`, `{filtered}`, `{total}`, `{page}`, `{pages}` |
| `data-table-count-template-all` | Shown when only count is enabled (no search/pagination): `{total}` |
| `data-table-empty` | Message when no rows match |
| `data-table-pagination-size` | `sm` or `lg` on generated `.pagination` |
| `data-table-export` | Export button (filtered rows → `.xlsx`, loads SheetJS on first click) |
| `data-table-refresh` | Reset search + pagination to page 1 |
| `data-table-export-label` | Export button label (default: `Export .xlsx`) |
| `data-table-refresh-label` | Refresh button label (default: `Refresh`) |
| `data-table-export-filename` | Download filename (default: `export.xlsx`) |
| `data-table-export-src` | Optional custom URL for SheetJS (`xlsx.full.min.js`) |

```html
<!-- Search only -->
<div class="table-datatable rounded" data-table-search>
  <div class="table-responsive">
    <table class="table rounded table-hover">...</table>
  </div>
</div>

<!-- Pagination + count -->
<div class="table-datatable rounded"
     data-table-pagination="5"
     data-table-count
     data-table-count-template="Showing {start}–{end} of {filtered} entries">
  <div class="table-responsive">
    <table class="table rounded table-striped table-hover">...</table>
  </div>
</div>
```

After adding rows dynamically, call `SuhuFramework.refreshTable(document.querySelector('.table-datatable'))`.

---

## Breadcrumb

```html
<nav>
  <ol class="breadcrumb rounded">
    <li class="breadcrumb-item"><a href="#">Home</a></li>
    <li class="breadcrumb-item"><a href="#">Section</a></li>
    <li class="breadcrumb-item active">Current Page</li>
  </ol>
</nav>
```

---

## Pagination

```html
<nav aria-label="Pagination">
  <ul class="pagination">
    <li class="page-item disabled"><a class="page-link rounded" href="#" aria-disabled="true">‹</a></li>
    <li class="page-item"><a class="page-link rounded" href="#">1</a></li>
    <li class="page-item active"><a class="page-link rounded" href="#" aria-current="page">2</a></li>
    <li class="page-item"><a class="page-link rounded" href="#">3</a></li>
    <li class="page-item"><a class="page-link rounded" href="#">›</a></li>
  </ul>
</nav>

<!-- Sizes -->
<ul class="pagination pagination-sm">...</ul>
<ul class="pagination pagination-lg">...</ul>
```

---

## Dropdowns

```html
<div class="dropdown">
  <button class="btn btn-primary dropdown-toggle">Menu ▾</button>
  <div class="dropdown-menu rounded">
    <div class="dropdown-header">Section</div>
    <a href="#" class="dropdown-item rounded">Option 1</a>
    <a href="#" class="dropdown-item rounded">Option 2</a>
    <div class="dropdown-divider"></div>
    <a href="#" class="dropdown-item rounded">Sign out</a>
  </div>
</div>

<!-- Right-aligned -->
<div class="dropdown dropdown-end">...</div>
```

---

## Utilities

### Spacing

| Pattern | Values | Example |
|---------|--------|---------|
| `.m-{n}` | 0-5 | `.m-4` → `margin: 1.5rem` |
| `.p-{n}` | 0-5 | `.p-3` → `padding: 1rem` |
| `.mt-`, `.mb-`, `.ms-`, `.me-` | 0-5 | Directional margin |
| `.pt-`, `.pb-`, `.ps-`, `.pe-` | 0-5 | Directional padding |
| `.mx-auto` | — | Center horizontally |
| `.gap-{n}` | 0-5 | Flex/grid gap |
| `.px-gutter` | — | Horizontal padding = `--sf-gutter` |
| `.px-container` | — | Horizontal padding = `--sf-container-padding` |

### Flexbox

```html
<div class="d-flex align-items-center justify-content-between gap-3">
  <span>Left</span>
  <span>Right</span>
</div>
```

### Text

```html
<p class="text-primary">Primary color</p>
<p class="fw-bold">Bold</p>
<p class="fs-4">Font size 4</p>
<p class="text-center">Centered</p>
<p class="text-truncate">Long text that will be truncated...</p>
```

### Background

```html
<div class="bg-primary text-white p-3">Solid bg</div>
<div class="bg-gradient-primary text-white p-3">Gradient bg</div>
<div class="bg-blue-subtle p-3">Subtle bg</div>
```

### Borders & Rounded

```html
<div class="border rounded">Default border + radius</div>
<div class="border border-primary rounded-lg">Primary border</div>
<div class="rounded-pill">Pill shape</div>
<div class="rounded-circle">Circle</div>
```

### Shadows

```html
<div class="shadow-sm">Small shadow</div>
<div class="shadow">Default shadow</div>
<div class="shadow-md">Medium shadow</div>
<div class="shadow-lg">Large shadow</div>
<div class="shadow-xl">XL shadow</div>
```

### Display

```html
<div class="d-none d-md-block">Hidden on mobile, visible on md+</div>
<div class="d-block d-lg-none">Visible on mobile/tablet, hidden on desktop</div>
```

---

## Animations

### CSS Animation Utilities

```html
<div class="animate-float">Float up/down</div>
<div class="animate-bounce">Bounce</div>
<div class="animate-pulse">Pulse opacity</div>
<div class="animate-spin">Spin</div>
<div class="animate-fade-in">Fade in</div>
<div class="animate-fade-in-up">Fade in from below</div>
<div class="animate-fade-in-down">Fade in from above</div>
<div class="animate-fade-in-left">Fade in from left</div>
<div class="animate-fade-in-right">Fade in from right</div>
<div class="animate-scale-in">Scale in</div>
```

### Scroll Reveal

Elements with `.reveal`, `.reveal-left`, or `.reveal-right` animate when they enter the viewport:

```html
<div class="reveal">Fade up on scroll</div>
<div class="reveal-left">Slide from left</div>
<div class="reveal-right">Slide from right</div>
```

**Stagger with delay utilities:**

```html
<div class="reveal delay-100">First (100ms delay)</div>
<div class="reveal delay-200">Second (200ms delay)</div>
<div class="reveal delay-300">Third (300ms delay)</div>
<div class="reveal delay-400">Fourth</div>
<div class="reveal delay-500">Fifth</div>
<div class="reveal delay-600">Sixth</div>
```

### Counter Animation

```html
<span data-count="1250" data-suffix="+" data-duration="1500">0</span>
<span data-count="99.9" data-suffix="%" data-prefix="~">0</span>
```

### Animated Gradient Background

```html
<div class="bg-animated-gradient">...</div>
```

### Mobile Animations

Touch-friendly enter/exit classes for mobile UI. See **`index.html` → Mobile Animations** (`#mobile-animations`).

**In:** `.mobile-fade-left-in`, `.mobile-fade-right-in`, `.mobile-fade-up-in`, `.mobile-fade-down-in`, `.mobile-scale-in`  
**Out:** `.mobile-fade-left-out`, `.mobile-fade-right-out`, `.mobile-fade-up-out`, `.mobile-fade-down-out`, `.mobile-scale-out`

### Tab Bar

Floating bottom navigation for mobile (&lt;992px). Fixed pages: `.tab-bar` + `.tab-bar-spacer`. Preview mockup: `.tab-bar-frame`. See **`index.html` → TabBar** (`#tab-bar`).

| Class | Purpose |
|-------|---------|
| `.tab-bar-item.active` | Current tab |
| `.tab-bar-icon` / `.tab-bar-label` | Icon + label |

---

## List Group

Flexible lists with active, disabled, action, and contextual variants. See **`index.html` → List group** (`#list-group`).

**Active item:** `.list-group-item.active` uses theme tokens plus a **3px start-edge accent** (`border-left: 3px solid var(--sf-blue-600)`; RTL uses `border-right`). Bottom border matches non-active items.

```html
<ul class="list-group">
  <li class="list-group-item active">Active item</li>
  <li class="list-group-item">Normal item</li>
  <li class="list-group-item disabled">Disabled</li>
</ul>

<div class="list-group">
  <a href="#" class="list-group-item list-group-item-action active">Components</a>
  <a href="#" class="list-group-item list-group-item-action">Settings</a>
</div>
```

**Variants:** `.list-group-flush`, `.list-group-item-action`, `.list-group-item-primary|success|danger|warning|secondary`

---

## JavaScript API

### `SuhuFramework.init()`

Auto-called on `DOMContentLoaded`. Initializes all components.

### Modal API

```js
const overlay = document.querySelector('#myModal');
SuhuFramework.openModal(overlay);   // Open
SuhuFramework.closeModal(overlay);  // Close
```

### Table API

```js
// Re-scan tbody rows after DOM updates (search / pagination / count)
SuhuFramework.refreshTable(document.querySelector('.table-datatable'));
```

### Data attributes

| Attribute | Element | Description |
|-----------|---------|-------------|
| `data-modal-target="#id"` | Button | Opens modal with given ID |
| `data-modal-close` | Button | Closes nearest parent modal-overlay |
| `data-modal-backdrop="static"` | `.modal-overlay` | Outside click does not close (shake feedback) |
| `data-bs-backdrop="static"` | `.modal-overlay` | SuhuFramework-compatible alias for static backdrop |
| `data-modal-keyboard="false"` | `.modal-overlay` | Disables ESC to close |
| `data-bs-keyboard="false"` | `.modal-overlay` | SuhuFramework-compatible alias |
| `data-bs-toggle="offcanvas"` | Button | Opens offcanvas (`data-bs-target="#id"`) |
| `data-toggle` | `.offcanvas-nav-link` | Toggles offcanvas submenu |
| `data-toggle` | `.offcanvas-submenu-link` | Toggles nested offcanvas menu |
| `data-width="75%"` | `.progress-bar` | Scroll-triggered width animation |
| `data-count="100"` | Any element | Counter animation target value |
| `data-suffix="%"` | Any element | Suffix for counter animation |
| `data-prefix="~"` | Any element | Prefix for counter animation |
| `data-duration="1500"` | Any element | Counter animation duration (ms) |
| `data-tooltip="text"` | Any element | CSS-only tooltip |
| `data-table-search` | `.table-datatable` | Enables client-side table search |
| `data-table-pagination="N"` | `.table-datatable` | Paginates tbody rows (`N` per page) |
| `data-table-count` | `.table-datatable` | Shows entry count in footer |
| `data-table-export` | `.table-datatable` | Export filtered rows to `.xlsx` |
| `data-table-refresh` | `.table-datatable` | Toolbar refresh (clears search & page) |
| `data-theme-mode-option` | Button | Sets theme: `light`, `dark`, or `auto` |
| `data-theme-toggle` | Button | Cycles light → dark → auto |
| `data-dir-option` | Button | Sets direction: `ltr` or `rtl` |
| `data-bs-toggle="popover"` | Button | Click popover with `data-bs-title` / `data-bs-content` |
| `data-multi-open` | `.accordion` | Allow multiple panels open |

---

## CSS Variables

Override any variable in your own stylesheet:

```css
:root {
  /* Brand colors */
  --primary:       #3b82f6;
  --primary-dark:  #2563eb;
  --secondary:     #64748b;
  --success:       #22c55e;
  --danger:        #ef4444;
  --warning:       #f59e0b;
  --info:          #06b6d4;

  /* Layout */
  --navbar-height: 4rem;
  --drawer-width:  300px;

  /* Radius */
  --radius-sm:   0.25rem;
  --radius:      0.5rem;
  --radius-md:   0.75rem;
  --radius-lg:   1rem;
  --radius-xl:   1.5rem;
  --radius-full: 9999px;

  /* Typography */
  --font-sans: 'Inter', sans-serif;
  --font-mono: 'Fira Code', monospace;

  /* Transitions */
  --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
  --transition:      250ms cubic-bezier(0.4, 0, 0.2, 1);
  --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
```

---

## Changelog

### v1.3.0 (2026-06-02)
- **Offcanvas** — SuhuFramework-compatible `.offcanvas` + `.offcanvas-start/end/top/bottom` component. Triggers via `data-bs-toggle="offcanvas"` + `data-bs-target="#id"`. Full sub-menu + nested-menu support via `.offcanvas-nav-item`, `.offcanvas-submenu`, `.offcanvas-nested-menu`. Slide-in/out animation (360ms) with backdrop fade. JS API: `SuhuFramework.openOffcanvas(id)` / `SuhuFramework.closeOffcanvas(id)`.
- **Navbar Menu renamed** — The demo navbar section is now "Navbar Menu". The mobile/tablet navigation uses the new Offcanvas component (`offcanvas-start`) instead of the legacy drawer, keeping backward compatibility via `.drawer` classes.
- **Carousel** — `new SFCarousel(selector, options)` (Swiper-style): spaceBetween, slidesPerView, gridRows, autoplay, zoom, thumbnail. Legacy `data-*` still supported.
- **Toast** — Stacked notification toasts with 4 colour variants (`toast-primary/success/danger/warning`). 6 position anchors via `.toast-container.top-right` etc. Auto-dismiss timer with animated progress bar. Trigger via `data-toast-*` attributes or `SuhuFramework.showToast({ title, message, type, duration, position })`.
- **Popover** — Click popovers via `data-bs-toggle="popover"` with `data-bs-title`, `data-bs-content`, and `data-bs-placement`. Dismiss with outside click or Escape. `SuhuFramework.closePopover()`.
- **Tooltip** — CSS-only tooltips via `data-tooltip="text"`. Position: `data-tooltip-pos="top|bottom|left|right"`. Wide text: `data-tooltip-wide`. Native `<button>` / submit inputs are auto-wrapped in `.sf-tooltip-host` on `SuhuFramework.init()` so the arrow is not clipped inside the control; or wrap manually: `<span class="sf-tooltip-host" data-tooltip="…"><button class="btn">…</button></span>`.

### v1.2.0 (2026-06-02)
- **Critical fix – syntax highlighter**: Rewrote `highlightCode()` using a **slot-based tokenizer**. Previously, the CSS property-value regex `/[\w-]+\s*:\s*/` matched `color:#f87171` inside the very `<span style="...">` tags it had just injected, causing every code preview to show raw colour literals instead of real code. Each processing step now stores its output in an indexed slot array (`\x00N\x00` placeholders), so no subsequent regex can re-process already-coloured HTML.
- **Highlight ordering**: comments → tag names → attribute names → attribute values → JS keywords → restore. Each step is guaranteed to only see plain text.
- **Mobile layout fixed**: `.navbar-actions` is now hidden on ≤991px (actions live inside the drawer). Prevents the desktop GitHub/CTA buttons from overflowing the mobile navbar.
- **Hero responsive**: title uses `clamp(2.25rem, 5.5vw + 0.5rem, 5.5rem)` so it never overflows small screens. `line-height` raised to `1.06` (was `1.0`) to prevent descender clipping.
- **Install snippet**: moved to `.sf-install-snippet` + `.sf-install-snippet-inner` with `overflow-x: auto` so it scrolls horizontally on mobile instead of breaking layout.
- **Stats row**: renders as a 2×2 grid on XS screens (<480px) with shared borders, exactly like SuhuFramework stat blocks.
- **Typography precision**: `font-optical-sizing: auto`, `line-height: 1.625` base, tighter clamp functions for all heading/display sizes, `font-feature-settings: 'cv11','salt','ss01'` applied in both framework and demo.
- **Responsive sections**: mobile-specific padding, font-size reductions, and layout fixes for hero, why-section, component-docs area at xs/sm/md breakpoints.
- **`html,body { overflow-x: hidden }`**: prevents any overflow from causing horizontal scrollbar.

### v1.4.0 (2026-06-02)
**New utility classes**
- `float-start`, `float-end`, `float-none`, `clearfix`
- `user-select-none/all/auto`, `pe-none`, `pe-auto` (Interactions)
- `link-primary/secondary/success/danger/warning/info/dark/body`, `link-underline`, `link-offset-*`
- `object-fit-contain/cover/fill/scale/none` (SuhuFramework utility naming)
- `overflow-visible/clip`, `overflow-x/y-hidden/scroll`
- `top-50/100`, `bottom-50/100`, `start-50/100`, `end-50/100`, `translate-middle/-x/-y`
- `vw-100`, `vh-100`, `min-vw-100`, `min-vh-100`, `w-33`, `w-66`
- `text-wrap`, `text-nowrap`, `text-break`, `text-reset`, `text-decoration-underline/line-through`, `fst-italic/normal`, `ls-tight/normal/wide/wider`
- `align-baseline/top/middle/bottom/text-top/text-bottom` (Vertical align)
- `z-n1`, `z-0`, `z-1`, `z-2`, `z-3` (SuhuFramework z-index scale)

**New components**
- **Collapse** — `data-bs-toggle="collapse"` + `data-bs-target`, with accordion mode via `data-bs-parent`
- **List Group** — `.list-group`, `.list-group-item`, flush/numbered/action/contextual variants
- **Scrollspy** — `data-bs-spy="scroll"` + `data-bs-target` auto-activates nav links on scroll
- **Input Group** — `.input-group` + `.input-group-text` for prepended/appended addons and buttons
- **Floating Labels** — `.form-floating` overlapping label on the top border (outlined / SuhuFramework UI style)
- **Validation** — `.is-valid`/`.is-invalid` + `.valid-feedback`/`.invalid-feedback`; supports `.was-validated` form class for HTML5 constraint API

**JS additions (suhu-framework.js)**
- `initCollapse()` — click delegation for `[data-bs-toggle="collapse"]`, handles `data-bs-parent` accordion
- `initScrollspy()` — `IntersectionObserver`-free, scroll-event based, supports both `body` and custom scroll containers
- Version bumped to `1.4.0`

**index.html updates**
- Sidebar now has "Utilities" and "Extended" groups with links for all 19 utility pages and 6 new component pages
- New demo sections for every utility and component with live previews + peek-code

### v1.0.0 (2026-06-03)

**SuhuFramework release**
- Product name standardized to **SuhuFramework** (`window.SuhuFramework`; `window.Suhu` kept as alias)
- All user-facing copy rebranded: no Tailwind or Bootstrap references in docs, demo, or CSS comments
- Version set to **1.0.0**
- Demo copy and component search strings in English
- `index.html` aligned with HTML5: semantic landmarks, `type="button"`, dialog ARIA, table `scope`, single document `<h1>`; passes [W3C Nu Html Checker](https://validator.w3.org/nu/) with zero errors

### v1.6.0 (2026-06-03)

**SuhuFramework UI (100% visual)**
- All component visuals live in a single `suhu-framework.css` stylesheet (no extra UI layer files)
- Design tokens aligned to SuhuFramework scale: `rounded-md` radii, `shadow-sm` / `shadow-xl`, ring borders, `focus-visible` rings
- Buttons: flat solids, white outline surfaces, no hover “lift” or ripple overlay
- Forms: full SuhuFramework look — ring-inset inputs, shadow-sm, rounded-md, focus ring-2 blue-600, checkbox/radio/switch/range/validation/floating labels
- Cards, tables, alerts, badges, navbar, modals, tabs, pagination, list-group, offcanvas, progress — all restyled
- Link **`suhu-framework.css` only** (includes SuhuFramework UI visuals)

### v1.5.0 (2026-06-03)

**Light & Dark Mode System**
- Added comprehensive semantic CSS custom properties for both themes (50+ tokens: `--sf-bg`, `--sf-surface`, `--sf-border`, `--sf-color-text`, `--sf-input-*`, `--sf-navbar-*`, `--sf-card-*`, `--sf-dropdown-*`, `--sf-modal-*`, `--sf-offcanvas-*`, `--sf-list-*`, etc.)
- Light mode is now the **default** (`:root` defines light values)
- Dark mode activates via `[data-theme="dark"]` on `<html>`
- All major components updated to use semantic tokens: Navbar, Cards, Forms, Accordion, Tabs, Tables, Modals, Dropdowns, Offcanvas, List Group, Input Group, Progress, Offcanvas nav links
- `initThemeToggle()` enhanced: auto-detects OS preference on first visit (`prefers-color-scheme`), persists choice to `localStorage`, listens for OS changes when user hasn't overridden
- Toggle buttons with `data-theme-toggle` automatically update `[data-theme-icon]` (☾/☀), `[data-theme-label]` (Dark/Light), `aria-label`, and `title`
- `body` and all theme-aware elements transition smoothly between modes (`transition: background-color 0.25s ease, color 0.25s ease`)
- Demo navbar now includes a theme toggle button
- All demo styles updated to use semantic tokens (sidebar, sections, code bars, footer, stats, hero, feature grid)
- Version bumped to `1.5.0`

**Theme fixes (v1.5.0 patch)**
- `.feature-card`, `.card-gradient-border`, `.toast`, `.chip`, `.stat-card`, `.btn-ghost`, list-group colour variants — all use semantic tokens; light mode no longer inherits dark-only styles
- Removed index.html global dark overrides that forced cards, forms, modals, tables, etc. to stay dark in light mode
- `.sf-preview--dark` corrected: light background in light mode, dark in dark mode
- Feature Card demo uses standard `.sf-preview` (not forced dark canvas)
- Added `.sf-demo-muted`, `.sf-demo-box`, `.sf-inline-code` helpers for theme-aware demo prose
- **Component search** on `index.html`: filter sidebar links by name or section id; mobile search bar; `⌘K` / `Ctrl+K` shortcut; `initComponentSearch()` in `suhu-framework.js`
- Scroll-spy sidebar selectors aligned to `.sf-sidebar` / `.sf-sidebar-link`
- **Component rounding**: `.rounded` / `.rounded-pill` on all framework surfaces; radii removed from component CSS defaults; `index.html` + `guide.md` updated; dynamic toasts include `.rounded`

**Dark mode audit (v1.0.0)**
- Form inputs: `--sf-input-bg` elevated above `--sf-surface` (`.sf-comp`), visible ring + shadow; select chevron preserved on valid/invalid; autofill styled
- Semantic tokens: `--sf-subtle-bg`, `--sf-hover-bg`, `--sf-progress-track`, `--sf-list-active-bg`, `--sf-list-active-text`
- SuhuFramework component merge block uses theme tokens (navbar, accordion, tabs, pagination, list-group, breadcrumb, tables, offcanvas, cards, inline `code`)
- Fixed `.list-group-item.active` contrast (was white text on light gray)
- Dark overrides for badges, alerts (info/secondary), list-group colour variants, `.bg-white` / `.bg-light`, utility `.bg-subtle` / `.bg-slate`
- Demo labels use `.sf-demo-label` / `.sf-demo-caption` (no hardcoded `#475569` on section headings)

### v1.1.0 (2026-06-02)
- **Critical fix**: Invalid CSS querySelector `'> selector'` now uses safe `Array.from(children).find()` — all interactive components now work
- **Visual redesign**: index.html fully dark-themed matching modern documentation sites (`#0B1120` background, gradient blobs, grid pattern)
- **Font improvement**: Added `font-feature-settings: 'cv11','salt','ss01'` for exact Inter rendering
- **JS hardening**: Each init function wrapped in try/catch — one failing component no longer breaks others
- **Tabs fix**: Tab content discovery now walks up the DOM tree correctly for both `.nav-tabs` and `.nav-pills`
- **Accordion**: Switched to event delegation on the accordion container for reliability

### v1.0.0 (2026-06-02)
- Initial **SuhuFramework** release
- Full component library: Navbar, Drawer, Grid, Typography, Buttons, Cards, Alerts, Badges, Chips, Forms, Modals, Accordion, Tabs, Progress, Spinners, Tables, Breadcrumb, Pagination, Dropdowns, Tooltip
- Mobile drawer with sub-menu and nested menu support (slide from left)
- Scroll reveal animations with IntersectionObserver
- Counter animation
- Progress bar scroll-triggered animation
- Code highlight utility
- Ripple effect on buttons
- Light/dark theme toggle (data-theme)
- Demo page with peek-code feature on all components
