Просмотр исходного кода

refactor: split forms/general into elements, layout, validation; add wizard

forms/general.html had grown into a 623-line monolith containing input
examples, layout patterns, and validation demos all on one page. Splits
it into three focused pages plus a brand-new wizard:

- forms/elements.html    Quick example, input groups, checks /
                         radios / switches, selects (single + multi) /
                         range / file / color, floating labels,
                         disabled / readonly
- forms/layout.html      Horizontal form, inline form (new), different
                         heights, different widths
- forms/validation.html  Custom .needs-validation pattern, browser-
                         default validation, .needs-validation-tooltip
                         variant (new). Shared submit-handler script.
- forms/wizard.html      Four-step wizard with per-step validation,
                         password-match check, animated step indicator
                         (CSS counters + pseudo-elements), and a final
                         review-and-confirm summary.

Drops the original forms/general.astro -- this is RC, so no stable
deprecation cycle required. Sidenav update lands separately.
Aigars Silkalns 14 часов назад
Родитель
Сommit
4954913e19

+ 345 - 0
src/html/pages/forms/elements.astro

@@ -0,0 +1,345 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "AdminLTE 4 | Form Elements"
+const path = "../../../dist"
+const mainPage = "forms"
+const page = "elements";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Form Elements</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Home</a></li>
+                  <li class="breadcrumb-item"><a href="#">Forms</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">Elements</li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row g-4">
+              <div class="col-12">
+                <div class="callout callout-info">
+                  For detailed documentation visit
+                  <a
+                    href="https://getbootstrap.com/docs/5.3/forms/overview/"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    class="callout-link"
+                  >Bootstrap Forms</a>.
+                </div>
+              </div>
+
+              <!-- Quick Example -->
+              <div class="col-md-6">
+                <div class="card card-primary card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Quick Example</div>
+                  </div>
+                  <form>
+                    <div class="card-body">
+                      <div class="mb-3">
+                        <label for="exampleInputEmail1" class="form-label">Email address</label>
+                        <input
+                          type="email"
+                          class="form-control"
+                          id="exampleInputEmail1"
+                          aria-describedby="emailHelp"
+                        />
+                        <div id="emailHelp" class="form-text">
+                          We'll never share your email with anyone else.
+                        </div>
+                      </div>
+                      <div class="mb-3">
+                        <label for="exampleInputPassword1" class="form-label">Password</label>
+                        <input
+                          type="password"
+                          class="form-control"
+                          id="exampleInputPassword1"
+                        />
+                      </div>
+                      <div class="input-group mb-3">
+                        <input
+                          type="file"
+                          class="form-control"
+                          id="inputGroupFile02"
+                        />
+                        <label class="input-group-text" for="inputGroupFile02">Upload</label>
+                      </div>
+                      <div class="mb-3 form-check">
+                        <input
+                          type="checkbox"
+                          class="form-check-input"
+                          id="exampleCheck1"
+                        />
+                        <label class="form-check-label" for="exampleCheck1">Check me out</label>
+                      </div>
+                    </div>
+                    <div class="card-footer">
+                      <button type="submit" class="btn btn-primary">Submit</button>
+                    </div>
+                  </form>
+                </div>
+              </div>
+
+              <!-- Input Group -->
+              <div class="col-md-6">
+                <div class="card card-success card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Input Group</div>
+                  </div>
+                  <div class="card-body">
+                    <div class="input-group mb-3">
+                      <span class="input-group-text" id="basic-addon1">@</span>
+                      <input
+                        type="text"
+                        class="form-control"
+                        placeholder="Username"
+                        aria-label="Username"
+                        aria-describedby="basic-addon1"
+                      />
+                    </div>
+                    <div class="input-group mb-3">
+                      <input
+                        type="text"
+                        class="form-control"
+                        placeholder="Recipient's username"
+                        aria-label="Recipient's username"
+                        aria-describedby="basic-addon2"
+                      />
+                      <span class="input-group-text" id="basic-addon2">@example.com</span>
+                    </div>
+                    <div class="mb-3">
+                      <label for="basic-url" class="form-label">Your vanity URL</label>
+                      <div class="input-group">
+                        <span class="input-group-text" id="basic-addon3">https://example.com/users/</span>
+                        <input
+                          type="text"
+                          class="form-control"
+                          id="basic-url"
+                          aria-describedby="basic-addon3 basic-addon4"
+                        />
+                      </div>
+                      <div class="form-text" id="basic-addon4">
+                        Example help text goes outside the input group.
+                      </div>
+                    </div>
+                    <div class="input-group mb-3">
+                      <span class="input-group-text">$</span>
+                      <input
+                        type="text"
+                        class="form-control"
+                        aria-label="Amount (to the nearest dollar)"
+                      />
+                      <span class="input-group-text">.00</span>
+                    </div>
+                    <div class="input-group mb-3">
+                      <input
+                        type="text"
+                        class="form-control"
+                        placeholder="Username"
+                        aria-label="Username"
+                      />
+                      <span class="input-group-text">@</span>
+                      <input
+                        type="text"
+                        class="form-control"
+                        placeholder="Server"
+                        aria-label="Server"
+                      />
+                    </div>
+                    <div class="input-group">
+                      <span class="input-group-text">With textarea</span>
+                      <textarea class="form-control" aria-label="With textarea"></textarea>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- Checks & Radios -->
+              <div class="col-md-6">
+                <div class="card card-info card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Checks &amp; Radios</div>
+                  </div>
+                  <div class="card-body">
+                    <h6 class="text-secondary small text-uppercase mb-2">Checkboxes</h6>
+                    <div class="form-check mb-3">
+                      <input class="form-check-input" type="checkbox" id="check-default" />
+                      <label class="form-check-label" for="check-default">Default</label>
+                    </div>
+                    <div class="form-check mb-3">
+                      <input class="form-check-input" type="checkbox" id="check-checked" checked />
+                      <label class="form-check-label" for="check-checked">Pre-checked</label>
+                    </div>
+                    <div class="form-check mb-3">
+                      <input class="form-check-input" type="checkbox" id="check-disabled" disabled />
+                      <label class="form-check-label" for="check-disabled">Disabled</label>
+                    </div>
+
+                    <h6 class="text-secondary small text-uppercase mb-2 mt-3">Radios</h6>
+                    <div class="form-check mb-2">
+                      <input class="form-check-input" type="radio" name="radioGroup" id="radio-1" checked />
+                      <label class="form-check-label" for="radio-1">Option one</label>
+                    </div>
+                    <div class="form-check mb-3">
+                      <input class="form-check-input" type="radio" name="radioGroup" id="radio-2" />
+                      <label class="form-check-label" for="radio-2">Option two</label>
+                    </div>
+
+                    <h6 class="text-secondary small text-uppercase mb-2 mt-3">Switches</h6>
+                    <div class="form-check form-switch mb-2">
+                      <input class="form-check-input" type="checkbox" role="switch" id="switch-1" />
+                      <label class="form-check-label" for="switch-1">Notifications</label>
+                    </div>
+                    <div class="form-check form-switch">
+                      <input class="form-check-input" type="checkbox" role="switch" id="switch-2" checked />
+                      <label class="form-check-label" for="switch-2">Auto-save</label>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- Select / Range / File -->
+              <div class="col-md-6">
+                <div class="card card-warning card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Selects, Ranges &amp; File</div>
+                  </div>
+                  <div class="card-body">
+                    <div class="mb-3">
+                      <label class="form-label" for="select-default">Select</label>
+                      <select class="form-select" id="select-default">
+                        <option selected>Open this select menu</option>
+                        <option>One</option>
+                        <option>Two</option>
+                        <option>Three</option>
+                      </select>
+                    </div>
+                    <div class="mb-3">
+                      <label class="form-label" for="select-multiple">Multi-select</label>
+                      <select class="form-select" id="select-multiple" multiple size="3">
+                        <option>Apple</option>
+                        <option selected>Orange</option>
+                        <option>Pear</option>
+                        <option>Mango</option>
+                      </select>
+                    </div>
+                    <div class="mb-3">
+                      <label class="form-label" for="range-1">Range</label>
+                      <input type="range" class="form-range" id="range-1" min="0" max="100" value="35" />
+                    </div>
+                    <div class="mb-3">
+                      <label class="form-label" for="form-file-multi">Multiple files</label>
+                      <input class="form-control" type="file" id="form-file-multi" multiple />
+                    </div>
+                    <div>
+                      <label class="form-label" for="form-color">Color</label>
+                      <input
+                        type="color"
+                        class="form-control form-control-color"
+                        id="form-color"
+                        value="#0d6efd"
+                        title="Choose your color"
+                      />
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- Floating labels -->
+              <div class="col-md-6">
+                <div class="card card-secondary card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Floating Labels</div>
+                  </div>
+                  <div class="card-body">
+                    <div class="form-floating mb-3">
+                      <input
+                        type="email"
+                        class="form-control"
+                        id="floatingEmail"
+                        placeholder="name@example.com"
+                      />
+                      <label for="floatingEmail">Email address</label>
+                    </div>
+                    <div class="form-floating mb-3">
+                      <input
+                        type="password"
+                        class="form-control"
+                        id="floatingPwd"
+                        placeholder="Password"
+                      />
+                      <label for="floatingPwd">Password</label>
+                    </div>
+                    <div class="form-floating">
+                      <textarea
+                        class="form-control"
+                        id="floatingTextarea"
+                        placeholder="Leave a comment here"
+                        style="height: 6rem;"></textarea>
+                      <label for="floatingTextarea">Comments</label>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- Disabled / Readonly -->
+              <div class="col-md-6">
+                <div class="card card-danger card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Disabled &amp; Readonly</div>
+                  </div>
+                  <div class="card-body">
+                    <div class="mb-3">
+                      <label class="form-label" for="dis-text">Disabled text</label>
+                      <input type="text" class="form-control" id="dis-text" value="Can't touch this" disabled />
+                    </div>
+                    <div class="mb-3">
+                      <label class="form-label" for="ro-text">Readonly text</label>
+                      <input type="text" class="form-control" id="ro-text" value="Read me, but don't write me" readonly />
+                    </div>
+                    <div class="mb-3">
+                      <label class="form-label" for="dis-select">Disabled select</label>
+                      <select id="dis-select" class="form-select" disabled>
+                        <option>Locked option</option>
+                      </select>
+                    </div>
+                    <div class="form-check">
+                      <input class="form-check-input" type="checkbox" id="dis-check" disabled />
+                      <label class="form-check-label" for="dis-check">Can't check this</label>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+  </body>
+</html>

+ 0 - 623
src/html/pages/forms/general.astro

@@ -1,623 +0,0 @@
----
-import Head from "@components/_head.astro"
-import Footer from "@components/dashboard/_footer.astro"
-import Topbar from "@components/dashboard/_topbar.astro"
-import Sidenav from "@components/dashboard/_sidenav.astro"
-import Scripts from "@components/_scripts.astro"
-
-const title = "AdminLTE 4 | General Form Elements"
-const path = "../../../dist"
-const mainPage = "forms"
-const page = "general";
----
-
-<!DOCTYPE html>
-<html lang="en">
-  <!--begin::Head-->
-  <head>
-    <Head title={title} path={path} />
-  </head>
-  <!--end::Head-->
-  <!--begin::Body-->
-  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
-    <!--begin::App Wrapper-->
-    <div class="app-wrapper">
-      <Topbar path={path} />
-      <Sidenav path={path} mainPage={mainPage} page={page} />
-      <!--begin::App Main-->
-      <main class="app-main">
-        <!--begin::App Content Header-->
-        <div class="app-content-header">
-          <!--begin::Container-->
-          <div class="container-fluid">
-            <!--begin::Row-->
-            <div class="row">
-              <div class="col-sm-6">
-                <h3 class="mb-0">General Form</h3>
-              </div>
-              <div class="col-sm-6">
-                <ol class="breadcrumb float-sm-end">
-                  <li class="breadcrumb-item"><a href="#">Home</a></li>
-                  <li class="breadcrumb-item active" aria-current="page">
-                    General Form
-                  </li>
-                </ol>
-              </div>
-            </div>
-            <!--end::Row-->
-          </div>
-          <!--end::Container-->
-        </div>
-        <!--end::App Content Header-->
-        <!--begin::App Content-->
-        <div class="app-content">
-          <!--begin::Container-->
-          <div class="container-fluid">
-            <!--begin::Row-->
-            <div class="row g-4">
-              <!--begin::Col-->
-              <div class="col-12">
-                <div class="callout callout-info">
-                  For detailed documentation of Form visit <a
-                    href="https://getbootstrap.com/docs/5.3/forms/overview/"
-                    target="_blank"
-                    rel="noopener noreferrer"
-                    class="callout-link"
-                  >
-                    Bootstrap Form
-                  </a>
-                </div>
-              </div>
-              <!--end::Col-->
-              <!--begin::Col-->
-              <div class="col-md-6">
-                <!--begin::Quick Example-->
-                <div class="card card-primary card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Quick Example</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Form-->
-                  <form>
-                    <!--begin::Body-->
-                    <div class="card-body">
-                      <div class="mb-3">
-                        <label for="exampleInputEmail1" class="form-label"
-                        >Email address</label
-                        >
-                        <input
-                          type="email"
-                          class="form-control"
-                          id="exampleInputEmail1"
-                          aria-describedby="emailHelp"
-                        />
-                        <div id="emailHelp" class="form-text">
-                          We'll never share your email with anyone else.
-                        </div>
-                      </div>
-                      <div class="mb-3">
-                        <label for="exampleInputPassword1" class="form-label"
-                        >Password</label
-                        >
-                        <input
-                          type="password"
-                          class="form-control"
-                          id="exampleInputPassword1"
-                        />
-                      </div>
-                      <div class="input-group mb-3">
-                        <input
-                          type="file"
-                          class="form-control"
-                          id="inputGroupFile02"
-                        />
-                        <label class="input-group-text" for="inputGroupFile02"
-                        >Upload</label
-                        >
-                      </div>
-                      <div class="mb-3 form-check">
-                        <input
-                          type="checkbox"
-                          class="form-check-input"
-                          id="exampleCheck1"
-                        />
-                        <label class="form-check-label" for="exampleCheck1"
-                        >Check me out</label
-                        >
-                      </div>
-                    </div>
-                    <!--end::Body-->
-                    <!--begin::Footer-->
-                    <div class="card-footer">
-                      <button type="submit" class="btn btn-primary"
-                      >Submit</button
-                      >
-                    </div>
-                    <!--end::Footer-->
-                  </form>
-                  <!--end::Form-->
-                </div>
-                <!--end::Quick Example-->
-                <!--begin::Input Group-->
-                <div class="card card-success card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Input Group</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Body-->
-                  <div class="card-body">
-                    <div class="input-group mb-3">
-                      <span class="input-group-text" id="basic-addon1">@</span>
-                      <input
-                        type="text"
-                        class="form-control"
-                        placeholder="Username"
-                        aria-label="Username"
-                        aria-describedby="basic-addon1"
-                      />
-                    </div>
-
-                    <div class="input-group mb-3">
-                      <input
-                        type="text"
-                        class="form-control"
-                        placeholder="Recipient's username"
-                        aria-label="Recipient's username"
-                        aria-describedby="basic-addon2"
-                      />
-                      <span class="input-group-text" id="basic-addon2"
-                      >@example.com</span
-                      >
-                    </div>
-
-                    <div class="mb-3">
-                      <label for="basic-url" class="form-label"
-                      >Your vanity URL</label
-                      >
-                      <div class="input-group">
-                        <span class="input-group-text" id="basic-addon3"
-                        >https://example.com/users/</span
-                        >
-                        <input
-                          type="text"
-                          class="form-control"
-                          id="basic-url"
-                          aria-describedby="basic-addon3 basic-addon4"
-                        />
-                      </div>
-                      <div class="form-text" id="basic-addon4">
-                        Example help text goes outside the input group.
-                      </div>
-                    </div>
-
-                    <div class="input-group mb-3">
-                      <span class="input-group-text">$</span>
-                      <input
-                        type="text"
-                        class="form-control"
-                        aria-label="Amount (to the nearest dollar)"
-                      />
-                      <span class="input-group-text">.00</span>
-                    </div>
-
-                    <div class="input-group mb-3">
-                      <input
-                        type="text"
-                        class="form-control"
-                        placeholder="Username"
-                        aria-label="Username"
-                      />
-                      <span class="input-group-text">@</span>
-                      <input
-                        type="text"
-                        class="form-control"
-                        placeholder="Server"
-                        aria-label="Server"
-                      />
-                    </div>
-
-                    <div class="input-group">
-                      <span class="input-group-text">With textarea</span>
-                      <textarea class="form-control" aria-label="With textarea"
-                      ></textarea>
-                    </div>
-                  </div>
-                  <!--end::Body-->
-                  <!--begin::Footer-->
-                  <div class="card-footer">
-                    <button type="submit" class="btn btn-success">Submit</button
-                    >
-                  </div>
-                  <!--end::Footer-->
-                </div>
-                <!--end::Input Group-->
-                <!--begin::Horizontal Form-->
-                <div class="card card-warning card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Horizontal Form</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Form-->
-                  <form>
-                    <!--begin::Body-->
-                    <div class="card-body">
-                      <div class="row mb-3">
-                        <label for="inputEmail3" class="col-sm-2 col-form-label"
-                        >Email</label
-                        >
-                        <div class="col-sm-10">
-                          <input
-                            type="email"
-                            class="form-control"
-                            id="inputEmail3"
-                          />
-                        </div>
-                      </div>
-                      <div class="row mb-3">
-                        <label
-                          for="inputPassword3"
-                          class="col-sm-2 col-form-label">Password</label
-                        >
-                        <div class="col-sm-10">
-                          <input
-                            type="password"
-                            class="form-control"
-                            id="inputPassword3"
-                          />
-                        </div>
-                      </div>
-                      <fieldset class="row mb-3">
-                        <legend class="col-form-label col-sm-2 pt-0"
-                        >Radios</legend
-                        >
-                        <div class="col-sm-10">
-                          <div class="form-check">
-                            <input
-                              class="form-check-input"
-                              type="radio"
-                              name="gridRadios"
-                              id="gridRadios1"
-                              value="option1"
-                              checked
-                            />
-                            <label class="form-check-label" for="gridRadios1">
-                              First radio
-                            </label>
-                          </div>
-                          <div class="form-check">
-                            <input
-                              class="form-check-input"
-                              type="radio"
-                              name="gridRadios"
-                              id="gridRadios2"
-                              value="option2"
-                            />
-                            <label class="form-check-label" for="gridRadios2">
-                              Second radio
-                            </label>
-                          </div>
-                          <div class="form-check disabled">
-                            <input
-                              class="form-check-input"
-                              type="radio"
-                              name="gridRadios"
-                              id="gridRadios3"
-                              value="option3"
-                              disabled
-                            />
-                            <label class="form-check-label" for="gridRadios3">
-                              Third disabled radio
-                            </label>
-                          </div>
-                        </div>
-                      </fieldset>
-                      <div class="row mb-3">
-                        <div class="col-sm-10 offset-sm-2">
-                          <div class="form-check">
-                            <input
-                              class="form-check-input"
-                              type="checkbox"
-                              id="gridCheck1"
-                            />
-                            <label class="form-check-label" for="gridCheck1">
-                              Example checkbox
-                            </label>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                    <!--end::Body-->
-                    <!--begin::Footer-->
-                    <div class="card-footer">
-                      <button type="submit" class="btn btn-warning"
-                      >Sign in</button
-                      >
-                      <button type="submit" class="btn float-end">Cancel</button
-                      >
-                    </div>
-                    <!--end::Footer-->
-                  </form>
-                  <!--end::Form-->
-                </div>
-                <!--end::Horizontal Form-->
-              </div>
-              <!--end::Col-->
-              <!--begin::Col-->
-              <div class="col-md-6">
-                <!--begin::Different Height-->
-                <div class="card card-secondary card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Different Height</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Body-->
-                  <div class="card-body">
-                    <input
-                      class="form-control form-control-lg"
-                      type="text"
-                      placeholder=".form-control-lg"
-                      aria-label=".form-control-lg example"
-                    />
-                    <br />
-                    <input
-                      class="form-control"
-                      type="text"
-                      placeholder="Default input"
-                      aria-label="default input example"
-                    />
-                    <br />
-                    <input
-                      class="form-control form-control-sm"
-                      type="text"
-                      placeholder=".form-control-sm"
-                      aria-label=".form-control-sm example"
-                    />
-                  </div>
-                  <!--end::Body-->
-                </div>
-                <!--end::Different Height-->
-                <!--begin::Different Width-->
-                <div class="card card-danger card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Different Width</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Body-->
-                  <div class="card-body">
-                    <!--begin::Row-->
-                    <div class="row">
-                      <!--begin::Col-->
-                      <div class="col-3">
-                        <input
-                          type="text"
-                          class="form-control"
-                          placeholder=".col-3"
-                        />
-                      </div>
-                      <!--end::Col-->
-                      <!--begin::Col-->
-                      <div class="col-4">
-                        <input
-                          type="text"
-                          class="form-control"
-                          placeholder=".col-4"
-                        />
-                      </div>
-                      <!--end::Col-->
-                      <!--begin::Col-->
-                      <div class="col-5">
-                        <input
-                          type="text"
-                          class="form-control"
-                          placeholder=".col-5"
-                        />
-                      </div>
-                      <!--end::Col-->
-                    </div>
-                    <!--end::Row-->
-                  </div>
-                  <!--end::Body-->
-                </div>
-                <!--end::Different Width-->
-                <!--begin::Form Validation-->
-                <div class="card card-info card-outline mb-4">
-                  <!--begin::Header-->
-                  <div class="card-header">
-                    <div class="card-title">Form Validation</div>
-                  </div>
-                  <!--end::Header-->
-                  <!--begin::Form-->
-                  <form class="needs-validation" novalidate>
-                    <!--begin::Body-->
-                    <div class="card-body">
-                      <!--begin::Row-->
-                      <div class="row g-3">
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label for="validationCustom01" class="form-label"
-                          >First name</label
-                          >
-                          <input
-                            type="text"
-                            class="form-control"
-                            id="validationCustom01"
-                            value="Mark"
-                            required
-                          />
-                          <div class="valid-feedback">Looks good!</div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label for="validationCustom02" class="form-label"
-                          >Last name</label
-                          >
-                          <input
-                            type="text"
-                            class="form-control"
-                            id="validationCustom02"
-                            value="Otto"
-                            required
-                          />
-                          <div class="valid-feedback">Looks good!</div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label
-                            for="validationCustomUsername"
-                            class="form-label">Username</label
-                          >
-                          <div class="input-group has-validation">
-                            <span
-                              class="input-group-text"
-                              id="inputGroupPrepend">@</span
-                            >
-                            <input
-                              type="text"
-                              class="form-control"
-                              id="validationCustomUsername"
-                              aria-describedby="inputGroupPrepend"
-                              required
-                            />
-                            <div class="invalid-feedback">
-                              Please choose a username.
-                            </div>
-                          </div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label for="validationCustom03" class="form-label"
-                          >City</label
-                          >
-                          <input
-                            type="text"
-                            class="form-control"
-                            id="validationCustom03"
-                            required
-                          />
-                          <div class="invalid-feedback">
-                            Please provide a valid city.
-                          </div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label for="validationCustom04" class="form-label"
-                          >State</label
-                          >
-                          <select
-                            class="form-select"
-                            id="validationCustom04"
-                            required
-                          >
-                            <option selected disabled value="">Choose...</option
-                            >
-                            <option>...</option>
-                          </select>
-                          <div class="invalid-feedback">
-                            Please select a valid state.
-                          </div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-md-6">
-                          <label for="validationCustom05" class="form-label"
-                          >Zip</label
-                          >
-                          <input
-                            type="text"
-                            class="form-control"
-                            id="validationCustom05"
-                            required
-                          />
-                          <div class="invalid-feedback">
-                            Please provide a valid zip.
-                          </div>
-                        </div>
-                        <!--end::Col-->
-                        <!--begin::Col-->
-                        <div class="col-12">
-                          <div class="form-check">
-                            <input
-                              class="form-check-input"
-                              type="checkbox"
-                              value=""
-                              id="invalidCheck"
-                              required
-                            />
-                            <label class="form-check-label" for="invalidCheck">
-                              Agree to terms and conditions
-                            </label>
-                            <div class="invalid-feedback">
-                              You must agree before submitting.
-                            </div>
-                          </div>
-                        </div>
-                        <!--end::Col-->
-                      </div>
-                      <!--end::Row-->
-                    </div>
-                    <!--end::Body-->
-                    <!--begin::Footer-->
-                    <div class="card-footer">
-                      <button class="btn btn-info" type="submit"
-                      >Submit form</button
-                      >
-                    </div>
-                    <!--end::Footer-->
-                  </form>
-                  <!--end::Form-->
-                  <!--begin::JavaScript-->
-                  <script is:inline>
-                    // Example starter JavaScript for disabling form submissions if there are invalid fields
-                    (() => {
-                      'use strict';
-
-                      // Fetch all the forms we want to apply custom Bootstrap validation styles to
-                      const forms = document.querySelectorAll('.needs-validation');
-
-                      // Loop over them and prevent submission
-                      Array.from(forms).forEach(form => {
-                        form.addEventListener(
-                          'submit',
-                          event => {
-                            if (!form.checkValidity()) {
-                              event.preventDefault();
-                              event.stopPropagation();
-                            }
-
-                            form.classList.add('was-validated');
-                          },
-                          false
-                        );
-                      });
-                    })();
-                  </script>
-                  <!--end::JavaScript-->
-                </div>
-                <!--end::Form Validation-->
-              </div>
-              <!--end::Col-->
-            </div>
-            <!--end::Row-->
-          </div>
-          <!--end::Container-->
-        </div>
-        <!--end::App Content-->
-      </main>
-      <!--end::App Main-->
-      <Footer />
-    </div>
-    <!--end::App Wrapper-->
-    <!--begin::Script-->
-    <Scripts path={path} />
-    <!--end::Script-->
-  </body><!--end::Body-->
-</html>

+ 230 - 0
src/html/pages/forms/layout.astro

@@ -0,0 +1,230 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "AdminLTE 4 | Form Layout"
+const path = "../../../dist"
+const mainPage = "forms"
+const page = "layout";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Form Layout</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Home</a></li>
+                  <li class="breadcrumb-item"><a href="#">Forms</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">Layout</li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row g-4">
+              <div class="col-12">
+                <div class="callout callout-info">
+                  Layout patterns built on Bootstrap's grid +
+                  <a
+                    href="https://getbootstrap.com/docs/5.3/forms/layout/"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    class="callout-link"
+                  >form layout</a>
+                  utilities.
+                </div>
+              </div>
+
+              <!-- Horizontal Form -->
+              <div class="col-md-6">
+                <div class="card card-warning card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Horizontal Form</div>
+                  </div>
+                  <form>
+                    <div class="card-body">
+                      <div class="row mb-3">
+                        <label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
+                        <div class="col-sm-10">
+                          <input type="email" class="form-control" id="inputEmail3" />
+                        </div>
+                      </div>
+                      <div class="row mb-3">
+                        <label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
+                        <div class="col-sm-10">
+                          <input type="password" class="form-control" id="inputPassword3" />
+                        </div>
+                      </div>
+                      <fieldset class="row mb-3">
+                        <legend class="col-form-label col-sm-2 pt-0">Radios</legend>
+                        <div class="col-sm-10">
+                          <div class="form-check">
+                            <input
+                              class="form-check-input"
+                              type="radio"
+                              name="gridRadios"
+                              id="gridRadios1"
+                              value="option1"
+                              checked
+                            />
+                            <label class="form-check-label" for="gridRadios1">First radio</label>
+                          </div>
+                          <div class="form-check">
+                            <input
+                              class="form-check-input"
+                              type="radio"
+                              name="gridRadios"
+                              id="gridRadios2"
+                              value="option2"
+                            />
+                            <label class="form-check-label" for="gridRadios2">Second radio</label>
+                          </div>
+                          <div class="form-check disabled">
+                            <input
+                              class="form-check-input"
+                              type="radio"
+                              name="gridRadios"
+                              id="gridRadios3"
+                              value="option3"
+                              disabled
+                            />
+                            <label class="form-check-label" for="gridRadios3">Third disabled radio</label>
+                          </div>
+                        </div>
+                      </fieldset>
+                      <div class="row mb-3">
+                        <div class="col-sm-10 offset-sm-2">
+                          <div class="form-check">
+                            <input class="form-check-input" type="checkbox" id="gridCheck1" />
+                            <label class="form-check-label" for="gridCheck1">Example checkbox</label>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="card-footer">
+                      <button type="submit" class="btn btn-warning">Sign in</button>
+                      <button type="submit" class="btn float-end">Cancel</button>
+                    </div>
+                  </form>
+                </div>
+              </div>
+
+              <!-- Inline Form -->
+              <div class="col-md-6">
+                <div class="card card-primary card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Inline Form</div>
+                  </div>
+                  <div class="card-body">
+                    <form class="row row-cols-lg-auto g-3 align-items-center">
+                      <div class="col-12">
+                        <label class="visually-hidden" for="inlineUser">Username</label>
+                        <div class="input-group">
+                          <div class="input-group-text">@</div>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="inlineUser"
+                            placeholder="Username"
+                          />
+                        </div>
+                      </div>
+                      <div class="col-12">
+                        <label class="visually-hidden" for="inlineSelect">Preference</label>
+                        <select class="form-select" id="inlineSelect">
+                          <option selected>Choose&hellip;</option>
+                          <option>One</option>
+                          <option>Two</option>
+                        </select>
+                      </div>
+                      <div class="col-12">
+                        <div class="form-check">
+                          <input class="form-check-input" type="checkbox" id="inlineCheck" />
+                          <label class="form-check-label" for="inlineCheck">Remember me</label>
+                        </div>
+                      </div>
+                      <div class="col-12">
+                        <button type="submit" class="btn btn-primary">Submit</button>
+                      </div>
+                    </form>
+                  </div>
+                </div>
+              </div>
+
+              <!-- Different Height -->
+              <div class="col-md-6">
+                <div class="card card-secondary card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Different Height</div>
+                  </div>
+                  <div class="card-body">
+                    <input
+                      class="form-control form-control-lg mb-3"
+                      type="text"
+                      placeholder=".form-control-lg"
+                      aria-label=".form-control-lg example"
+                    />
+                    <input
+                      class="form-control mb-3"
+                      type="text"
+                      placeholder="Default input"
+                      aria-label="default input example"
+                    />
+                    <input
+                      class="form-control form-control-sm"
+                      type="text"
+                      placeholder=".form-control-sm"
+                      aria-label=".form-control-sm example"
+                    />
+                  </div>
+                </div>
+              </div>
+
+              <!-- Different Width -->
+              <div class="col-md-6">
+                <div class="card card-danger card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Different Width</div>
+                  </div>
+                  <div class="card-body">
+                    <div class="row g-2">
+                      <div class="col-3">
+                        <input type="text" class="form-control" placeholder=".col-3" />
+                      </div>
+                      <div class="col-4">
+                        <input type="text" class="form-control" placeholder=".col-4" />
+                      </div>
+                      <div class="col-5">
+                        <input type="text" class="form-control" placeholder=".col-5" />
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+  </body>
+</html>

+ 258 - 0
src/html/pages/forms/validation.astro

@@ -0,0 +1,258 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "AdminLTE 4 | Form Validation"
+const path = "../../../dist"
+const mainPage = "forms"
+const page = "validation";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Form Validation</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Home</a></li>
+                  <li class="breadcrumb-item"><a href="#">Forms</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">Validation</li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row g-4">
+              <div class="col-12">
+                <div class="callout callout-info">
+                  Built on
+                  <a
+                    href="https://getbootstrap.com/docs/5.3/forms/validation/"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    class="callout-link"
+                  >Bootstrap's native form validation</a>
+                  — submit each form to see the feedback. The wiring script lives at the bottom of this page.
+                </div>
+              </div>
+
+              <!-- Custom validation -->
+              <div class="col-lg-6">
+                <div class="card card-info card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Custom Validation</div>
+                  </div>
+                  <form class="needs-validation" novalidate>
+                    <div class="card-body">
+                      <div class="row g-3">
+                        <div class="col-md-6">
+                          <label for="validationCustom01" class="form-label">First name</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="validationCustom01"
+                            value="Mark"
+                            required
+                          />
+                          <div class="valid-feedback">Looks good!</div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="validationCustom02" class="form-label">Last name</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="validationCustom02"
+                            value="Otto"
+                            required
+                          />
+                          <div class="valid-feedback">Looks good!</div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="validationCustomUsername" class="form-label">Username</label>
+                          <div class="input-group has-validation">
+                            <span class="input-group-text" id="inputGroupPrepend">@</span>
+                            <input
+                              type="text"
+                              class="form-control"
+                              id="validationCustomUsername"
+                              aria-describedby="inputGroupPrepend"
+                              required
+                            />
+                            <div class="invalid-feedback">Please choose a username.</div>
+                          </div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="validationCustom03" class="form-label">City</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="validationCustom03"
+                            required
+                          />
+                          <div class="invalid-feedback">Please provide a valid city.</div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="validationCustom04" class="form-label">State</label>
+                          <select
+                            class="form-select"
+                            id="validationCustom04"
+                            required
+                          >
+                            <option selected disabled value="">Choose&hellip;</option>
+                            <option>California</option>
+                            <option>Washington</option>
+                            <option>Tennessee</option>
+                          </select>
+                          <div class="invalid-feedback">Please select a valid state.</div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="validationCustom05" class="form-label">Zip</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="validationCustom05"
+                            required
+                          />
+                          <div class="invalid-feedback">Please provide a valid zip.</div>
+                        </div>
+                        <div class="col-12">
+                          <div class="form-check">
+                            <input
+                              class="form-check-input"
+                              type="checkbox"
+                              value=""
+                              id="invalidCheck"
+                              required
+                            />
+                            <label class="form-check-label" for="invalidCheck">
+                              Agree to terms and conditions
+                            </label>
+                            <div class="invalid-feedback">You must agree before submitting.</div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="card-footer">
+                      <button class="btn btn-info" type="submit">Submit form</button>
+                    </div>
+                  </form>
+                </div>
+              </div>
+
+              <!-- Browser-default validation + tooltips -->
+              <div class="col-lg-6">
+                <div class="card card-success card-outline mb-4">
+                  <div class="card-header">
+                    <div class="card-title">Browser Defaults &amp; Tooltips</div>
+                  </div>
+                  <div class="card-body">
+                    <p class="text-secondary small">
+                      For browser-native validation feedback, omit
+                      <code>novalidate</code> and the <code>.needs-validation</code> class.
+                    </p>
+                    <form>
+                      <div class="mb-3">
+                        <label class="form-label" for="defaultEmail">Email</label>
+                        <input
+                          type="email"
+                          class="form-control"
+                          id="defaultEmail"
+                          required
+                        />
+                      </div>
+                      <div class="mb-3">
+                        <label class="form-label" for="defaultUrl">URL</label>
+                        <input
+                          type="url"
+                          class="form-control"
+                          id="defaultUrl"
+                          required
+                        />
+                      </div>
+                      <button class="btn btn-success" type="submit">Submit (browser default)</button>
+                    </form>
+
+                    <hr class="my-4" />
+
+                    <p class="text-secondary small">
+                      Add <code>.needs-validation-tooltip</code> in place of
+                      <code>.needs-validation</code> to swap inline feedback for floating tooltips.
+                    </p>
+                    <form class="needs-validation-tooltip position-relative" novalidate>
+                      <div class="row g-3">
+                        <div class="col-md-6">
+                          <label for="vt-first" class="form-label">First name</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="vt-first"
+                            value="Jane"
+                            required
+                          />
+                          <div class="valid-tooltip">Looks good!</div>
+                        </div>
+                        <div class="col-md-6">
+                          <label for="vt-username" class="form-label">Username</label>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="vt-username"
+                            required
+                          />
+                          <div class="invalid-tooltip">Please choose a username.</div>
+                        </div>
+                        <div class="col-12">
+                          <button class="btn btn-success" type="submit">Submit (tooltip)</button>
+                        </div>
+                      </div>
+                    </form>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+    <script is:inline>
+      // Enable Bootstrap-style validation for forms marked with .needs-validation
+      // or .needs-validation-tooltip. Prevents submission if any field is invalid.
+      ;(() => {
+        "use strict"
+        const selector = ".needs-validation, .needs-validation-tooltip"
+        for (const form of document.querySelectorAll(selector)) {
+          form.addEventListener(
+            "submit",
+            (event) => {
+              if (!form.checkValidity()) {
+                event.preventDefault()
+                event.stopPropagation()
+              }
+              form.classList.add("was-validated")
+            },
+            false
+          )
+        }
+      })()
+    </script>
+  </body>
+</html>

+ 438 - 0
src/html/pages/forms/wizard.astro

@@ -0,0 +1,438 @@
+---
+import Head from "@components/_head.astro"
+import Footer from "@components/dashboard/_footer.astro"
+import Topbar from "@components/dashboard/_topbar.astro"
+import Sidenav from "@components/dashboard/_sidenav.astro"
+import Scripts from "@components/_scripts.astro"
+
+const title = "AdminLTE 4 | Form Wizard"
+const path = "../../../dist"
+const mainPage = "forms"
+const page = "wizard";
+---
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <Head title={title} path={path} />
+    <style is:inline>
+      .wizard-steps {
+        counter-reset: step;
+        list-style: none;
+        padding: 0;
+        display: flex;
+        justify-content: space-between;
+        position: relative;
+      }
+      .wizard-steps::before {
+        content: "";
+        position: absolute;
+        top: 1rem;
+        left: 0;
+        right: 0;
+        height: 2px;
+        background: var(--bs-border-color);
+        z-index: 0;
+      }
+      .wizard-steps li {
+        position: relative;
+        z-index: 1;
+        background: var(--bs-body-bg);
+        padding: 0 .75rem;
+        text-align: center;
+        color: var(--bs-secondary-color);
+        font-size: .875rem;
+      }
+      .wizard-steps li::before {
+        counter-increment: step;
+        content: counter(step);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 2rem;
+        height: 2rem;
+        margin: 0 auto .5rem;
+        border-radius: 50%;
+        background: var(--bs-body-tertiary-bg);
+        border: 2px solid var(--bs-border-color);
+        color: var(--bs-secondary-color);
+        font-weight: 600;
+      }
+      .wizard-steps li.active {
+        color: var(--bs-primary);
+        font-weight: 600;
+      }
+      .wizard-steps li.active::before {
+        background: var(--bs-primary);
+        border-color: var(--bs-primary);
+        color: #fff;
+      }
+      .wizard-steps li.completed::before {
+        background: var(--bs-success);
+        border-color: var(--bs-success);
+        color: #fff;
+        content: "\f633";
+        font-family: "bootstrap-icons";
+      }
+    </style>
+  </head>
+  <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
+    <div class="app-wrapper">
+      <Topbar path={path} />
+      <Sidenav path={path} mainPage={mainPage} page={page} />
+      <main class="app-main">
+        <div class="app-content-header">
+          <div class="container-fluid">
+            <div class="row">
+              <div class="col-sm-6">
+                <h3 class="mb-0">Form Wizard</h3>
+              </div>
+              <div class="col-sm-6">
+                <ol class="breadcrumb float-sm-end">
+                  <li class="breadcrumb-item"><a href="#">Home</a></li>
+                  <li class="breadcrumb-item"><a href="#">Forms</a></li>
+                  <li class="breadcrumb-item active" aria-current="page">Wizard</li>
+                </ol>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="app-content">
+          <div class="container-fluid">
+            <div class="row justify-content-center">
+              <div class="col-lg-10 col-xl-8">
+                <div class="card">
+                  <div class="card-body p-4">
+                    <!-- Step indicators -->
+                    <ol class="wizard-steps mb-4" id="wizard-steps">
+                      <li class="active" data-step="0">Account</li>
+                      <li data-step="1">Profile</li>
+                      <li data-step="2">Preferences</li>
+                      <li data-step="3">Review</li>
+                    </ol>
+
+                    <!-- Form -->
+                    <form id="wizard-form" novalidate>
+                      <!-- Step 1 -->
+                      <fieldset class="wizard-step" data-step="0">
+                        <h2 class="h5 mb-3">Create your account</h2>
+                        <div class="row g-3">
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-email">Email</label>
+                            <input
+                              type="email"
+                              class="form-control"
+                              id="wz-email"
+                              required
+                            />
+                            <div class="invalid-feedback">
+                              Please enter a valid email.
+                            </div>
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-username">
+                              Username
+                            </label>
+                            <input
+                              type="text"
+                              class="form-control"
+                              id="wz-username"
+                              required
+                              minlength="3"
+                            />
+                            <div class="invalid-feedback">
+                              Username must be at least 3 characters.
+                            </div>
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-password">
+                              Password
+                            </label>
+                            <input
+                              type="password"
+                              class="form-control"
+                              id="wz-password"
+                              required
+                              minlength="8"
+                            />
+                            <div class="invalid-feedback">
+                              Password must be at least 8 characters.
+                            </div>
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-password2">
+                              Confirm password
+                            </label>
+                            <input
+                              type="password"
+                              class="form-control"
+                              id="wz-password2"
+                              required
+                            />
+                            <div class="invalid-feedback">
+                              Passwords must match.
+                            </div>
+                          </div>
+                        </div>
+                      </fieldset>
+
+                      <!-- Step 2 -->
+                      <fieldset class="wizard-step d-none" data-step="1">
+                        <h2 class="h5 mb-3">Tell us about yourself</h2>
+                        <div class="row g-3">
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-first">
+                              First name
+                            </label>
+                            <input
+                              type="text"
+                              class="form-control"
+                              id="wz-first"
+                              required
+                            />
+                            <div class="invalid-feedback">
+                              First name is required.
+                            </div>
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-last">
+                              Last name
+                            </label>
+                            <input
+                              type="text"
+                              class="form-control"
+                              id="wz-last"
+                              required
+                            />
+                            <div class="invalid-feedback">
+                              Last name is required.
+                            </div>
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-company">
+                              Company
+                            </label>
+                            <input
+                              type="text"
+                              class="form-control"
+                              id="wz-company"
+                            />
+                          </div>
+                          <div class="col-md-6">
+                            <label class="form-label" for="wz-role">
+                              Role
+                            </label>
+                            <select class="form-select" id="wz-role" required>
+                              <option value="">Choose&hellip;</option>
+                              <option>Founder / CEO</option>
+                              <option>Engineering</option>
+                              <option>Design</option>
+                              <option>Marketing</option>
+                              <option>Other</option>
+                            </select>
+                            <div class="invalid-feedback">
+                              Please select a role.
+                            </div>
+                          </div>
+                        </div>
+                      </fieldset>
+
+                      <!-- Step 3 -->
+                      <fieldset class="wizard-step d-none" data-step="2">
+                        <h2 class="h5 mb-3">Notification preferences</h2>
+                        <div class="form-check form-switch mb-2">
+                          <input
+                            class="form-check-input"
+                            type="checkbox"
+                            id="wz-notif-product"
+                            role="switch"
+                            checked
+                          />
+                          <label class="form-check-label" for="wz-notif-product">
+                            Product updates &amp; releases
+                          </label>
+                        </div>
+                        <div class="form-check form-switch mb-2">
+                          <input
+                            class="form-check-input"
+                            type="checkbox"
+                            id="wz-notif-security"
+                            role="switch"
+                            checked
+                          />
+                          <label class="form-check-label" for="wz-notif-security">
+                            Security alerts
+                          </label>
+                        </div>
+                        <div class="form-check form-switch mb-3">
+                          <input
+                            class="form-check-input"
+                            type="checkbox"
+                            id="wz-notif-marketing"
+                            role="switch"
+                          />
+                          <label class="form-check-label" for="wz-notif-marketing">
+                            Marketing &amp; tips
+                          </label>
+                        </div>
+                        <label class="form-label" for="wz-frequency">
+                          Digest frequency
+                        </label>
+                        <select class="form-select" id="wz-frequency">
+                          <option>Real time</option>
+                          <option selected>Daily</option>
+                          <option>Weekly</option>
+                          <option>Never</option>
+                        </select>
+                      </fieldset>
+
+                      <!-- Step 4 -->
+                      <fieldset class="wizard-step d-none" data-step="3">
+                        <h2 class="h5 mb-3">Review &amp; confirm</h2>
+                        <dl class="row mb-3" id="wz-summary"></dl>
+                        <div class="form-check">
+                          <input
+                            class="form-check-input"
+                            type="checkbox"
+                            id="wz-terms"
+                            required
+                          />
+                          <label class="form-check-label" for="wz-terms">
+                            I agree to the <a href="#">terms of service</a>.
+                          </label>
+                          <div class="invalid-feedback">
+                            You must accept the terms to continue.
+                          </div>
+                        </div>
+                      </fieldset>
+
+                      <!-- Navigation -->
+                      <div class="d-flex justify-content-between mt-4">
+                        <button
+                          type="button"
+                          class="btn btn-outline-secondary"
+                          id="wz-prev"
+                          disabled
+                        >
+                          <i class="bi bi-arrow-left me-1" aria-hidden="true"></i>
+                          Previous
+                        </button>
+                        <button
+                          type="button"
+                          class="btn btn-primary"
+                          id="wz-next"
+                        >
+                          Next
+                          <i class="bi bi-arrow-right ms-1" aria-hidden="true"></i>
+                        </button>
+                        <button
+                          type="submit"
+                          class="btn btn-success d-none"
+                          id="wz-submit"
+                        >
+                          <i class="bi bi-check-lg me-1" aria-hidden="true"></i>
+                          Submit
+                        </button>
+                      </div>
+                    </form>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </main>
+      <Footer />
+    </div>
+    <Scripts path={path} />
+    <script is:inline>
+      document.addEventListener("DOMContentLoaded", () => {
+        const form = document.getElementById("wizard-form");
+        const steps = form.querySelectorAll(".wizard-step");
+        const indicators = document.querySelectorAll("#wizard-steps li");
+        const prevBtn = document.getElementById("wz-prev");
+        const nextBtn = document.getElementById("wz-next");
+        const submitBtn = document.getElementById("wz-submit");
+        let current = 0;
+
+        const show = (i) => {
+          steps.forEach((s, idx) => s.classList.toggle("d-none", idx !== i));
+          indicators.forEach((li, idx) => {
+            li.classList.toggle("active", idx === i);
+            li.classList.toggle("completed", idx < i);
+          });
+          prevBtn.disabled = i === 0;
+          const last = i === steps.length - 1;
+          nextBtn.classList.toggle("d-none", last);
+          submitBtn.classList.toggle("d-none", !last);
+          if (last) renderSummary();
+        };
+
+        const validateStep = (i) => {
+          const step = steps[i];
+          const fields = step.querySelectorAll("input, select, textarea");
+          let valid = true;
+          fields.forEach((field) => {
+            field.classList.remove("is-invalid");
+            if (!field.checkValidity()) {
+              field.classList.add("is-invalid");
+              valid = false;
+            }
+          });
+          // Password match check on step 0
+          if (i === 0) {
+            const p1 = document.getElementById("wz-password");
+            const p2 = document.getElementById("wz-password2");
+            if (p1.value !== p2.value) {
+              p2.classList.add("is-invalid");
+              valid = false;
+            }
+          }
+          return valid;
+        };
+
+        const renderSummary = () => {
+          const summary = document.getElementById("wz-summary");
+          const get = (id) => document.getElementById(id);
+          const rows = [
+            ["Email", get("wz-email").value],
+            ["Username", get("wz-username").value],
+            ["Name", `${get("wz-first").value} ${get("wz-last").value}`],
+            ["Company", get("wz-company").value || "—"],
+            ["Role", get("wz-role").value || "—"],
+            ["Digest", get("wz-frequency").value]
+          ];
+          summary.innerHTML = rows
+            .map(
+              ([k, v]) =>
+                `<dt class="col-sm-4 text-secondary fw-normal">${k}</dt><dd class="col-sm-8 fw-semibold">${v}</dd>`
+            )
+            .join("");
+        };
+
+        nextBtn.addEventListener("click", () => {
+          if (!validateStep(current)) return;
+          if (current < steps.length - 1) {
+            current++;
+            show(current);
+          }
+        });
+
+        prevBtn.addEventListener("click", () => {
+          if (current > 0) {
+            current--;
+            show(current);
+          }
+        });
+
+        form.addEventListener("submit", (e) => {
+          e.preventDefault();
+          if (!validateStep(current)) return;
+          alert("Wizard complete! Form would submit here.");
+        });
+
+        show(0);
+      });
+    </script>
+  </body>
+</html>