feat: Renames quick calc routes / views to ductulator. Adds button to home page for using ductulator, needs added to navbar still.

This commit is contained in:
2026-02-09 16:36:24 -05:00
parent 007d13be2f
commit 06b663052e
10 changed files with 235 additions and 137 deletions

View File

@@ -8,8 +8,6 @@
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace; monospace;
--color-green-400: oklch(79.2% 0.209 151.711); --color-green-400: oklch(79.2% 0.209 151.711);
--color-sky-600: oklch(58.8% 0.158 241.966);
--color-violet-600: oklch(54.1% 0.281 293.009);
--color-gray-200: oklch(92.8% 0.006 264.531); --color-gray-200: oklch(92.8% 0.006 264.531);
--color-gray-400: oklch(70.7% 0.022 261.325); --color-gray-400: oklch(70.7% 0.022 261.325);
--color-black: #000; --color-black: #000;
@@ -5304,6 +5302,12 @@
} }
} }
} }
.-mx-2 {
margin-inline: calc(var(--spacing) * -2);
}
.-mx-4 {
margin-inline: calc(var(--spacing) * -4);
}
.mx-10 { .mx-10 {
margin-inline: calc(var(--spacing) * 10); margin-inline: calc(var(--spacing) * 10);
} }
@@ -5399,12 +5403,18 @@
} }
} }
} }
.-my-4 {
margin-block: calc(var(--spacing) * -4);
}
.my-1\.5 { .my-1\.5 {
margin-block: calc(var(--spacing) * 1.5); margin-block: calc(var(--spacing) * 1.5);
} }
.my-6 { .my-6 {
margin-block: calc(var(--spacing) * 6); margin-block: calc(var(--spacing) * 6);
} }
.my-8 {
margin-block: calc(var(--spacing) * 8);
}
.my-auto { .my-auto {
margin-block: auto; margin-block: auto;
} }
@@ -5620,9 +5630,6 @@
.me-4 { .me-4 {
margin-inline-end: calc(var(--spacing) * 4); margin-inline-end: calc(var(--spacing) * 4);
} }
.me-6 {
margin-inline-end: calc(var(--spacing) * 6);
}
.me-10 { .me-10 {
margin-inline-end: calc(var(--spacing) * 10); margin-inline-end: calc(var(--spacing) * 10);
} }
@@ -5686,6 +5693,12 @@
.mt-10 { .mt-10 {
margin-top: calc(var(--spacing) * 10); margin-top: calc(var(--spacing) * 10);
} }
.mt-20 {
margin-top: calc(var(--spacing) * 20);
}
.mt-30 {
margin-top: calc(var(--spacing) * 30);
}
.breadcrumbs { .breadcrumbs {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
max-width: 100%; max-width: 100%;
@@ -6707,12 +6720,6 @@
.w-px { .w-px {
width: 1px; width: 1px;
} }
.max-w-lg {
max-width: var(--container-lg);
}
.max-w-xl {
max-width: var(--container-xl);
}
.min-w-0 { .min-w-0 {
min-width: calc(var(--spacing) * 0); min-width: calc(var(--spacing) * 0);
} }
@@ -6990,13 +6997,6 @@
.gap-4 { .gap-4 {
gap: calc(var(--spacing) * 4); gap: calc(var(--spacing) * 4);
} }
.space-y-1 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-2 { .space-y-2 {
:where(& > :not(:last-child)) { :where(& > :not(:last-child)) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
@@ -7642,9 +7642,6 @@
} }
} }
} }
.bg-base-100 {
background-color: var(--color-base-100);
}
.bg-base-200 { .bg-base-200 {
background-color: var(--color-base-200); background-color: var(--color-base-200);
} }
@@ -8779,9 +8776,6 @@
color: var(--color-warning); color: var(--color-warning);
} }
} }
.text-accent {
color: var(--color-accent);
}
.text-base-content { .text-base-content {
color: var(--color-base-content); color: var(--color-base-content);
} }
@@ -9751,16 +9745,21 @@
} }
} }
} }
.md\:mt-6 {
@media (width >= 48rem) {
margin-top: calc(var(--spacing) * 6);
}
}
.md\:mt-15 {
@media (width >= 48rem) {
margin-top: calc(var(--spacing) * 15);
}
}
.md\:grid-cols-2 { .md\:grid-cols-2 {
@media (width >= 48rem) { @media (width >= 48rem) {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
} }
.md\:grid-cols-3 {
@media (width >= 48rem) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.lg\:drawer-open { .lg\:drawer-open {
@media (width >= 64rem) { @media (width >= 64rem) {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
@@ -9814,9 +9813,14 @@
} }
} }
} }
.lg\:grid-cols-4 { .lg\:mx-20 {
@media (width >= 64rem) { @media (width >= 64rem) {
grid-template-columns: repeat(4, minmax(0, 1fr)); margin-inline: calc(var(--spacing) * 20);
}
}
.lg\:mt-6 {
@media (width >= 64rem) {
margin-top: calc(var(--spacing) * 6);
} }
} }
.is-drawer-close\:mx-auto { .is-drawer-close\:mx-auto {
@@ -9858,11 +9862,6 @@
overflow: visible; overflow: visible;
} }
} }
.is-drawer-close\:text-green-400 {
&:where(.drawer-toggle:not(:checked) ~ .drawer-side, .drawer-toggle:not(:checked) ~ .drawer-side *) {
color: var(--color-green-400);
}
}
.is-drawer-open\:flex { .is-drawer-open\:flex {
&:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) { &:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) {
display: flex; display: flex;

View File

@@ -14,7 +14,7 @@ private let viewRouteMiddleware: [any Middleware] = [
extension SiteRoute.View { extension SiteRoute.View {
var middleware: [any Middleware]? { var middleware: [any Middleware]? {
switch self { switch self {
case .home, .login, .signup, .test, .quickCalc: case .home, .login, .signup, .test, .ductulator:
return nil return nil
case .project, .user: case .project, .user:
return viewRouteMiddleware return viewRouteMiddleware

View File

@@ -12,7 +12,7 @@ extension SiteRoute {
case login(LoginRoute) case login(LoginRoute)
case signup(SignupRoute) case signup(SignupRoute)
case project(ProjectRoute) case project(ProjectRoute)
case quickCalc(QuickCalcRoute) case ductulator(DuctulatorRoute)
case user(UserRoute) case user(UserRoute)
//FIX: Remove. //FIX: Remove.
case test case test
@@ -34,8 +34,8 @@ extension SiteRoute {
Route(.case(Self.project)) { Route(.case(Self.project)) {
SiteRoute.View.ProjectRoute.router SiteRoute.View.ProjectRoute.router
} }
Route(.case(Self.quickCalc)) { Route(.case(Self.ductulator)) {
SiteRoute.View.QuickCalcRoute.router SiteRoute.View.DuctulatorRoute.router
} }
Route(.case(Self.user)) { Route(.case(Self.user)) {
SiteRoute.View.UserRoute.router SiteRoute.View.UserRoute.router
@@ -991,7 +991,7 @@ extension SiteRoute.View.UserRoute {
} }
extension SiteRoute.View { extension SiteRoute.View {
public enum QuickCalcRoute: Equatable, Sendable { public enum DuctulatorRoute: Equatable, Sendable {
case index case index
case submit(Form) case submit(Form)

View File

@@ -1,4 +1,6 @@
import Elementary import Elementary
import ElementaryHTMX
import ManualDCore
public struct SubmitButton: HTML, Sendable { public struct SubmitButton: HTML, Sendable {
let title: String let title: String
@@ -74,3 +76,17 @@ public struct TrashButton: HTML, Sendable {
} }
} }
} }
public struct DuctulatorButton: HTML, Sendable {
public init() {}
public var body: some HTML<HTMLTag.a> {
a(
.class("btn"),
.href(route: .ductulator(.index)),
.target(.blank)
) {
"Ductulator"
}
}
}

View File

@@ -96,7 +96,7 @@ extension ViewController.Request {
case .project(let route): case .project(let route):
return await route.renderView(on: self) return await route.renderView(on: self)
case .quickCalc(let route): case .ductulator(let route):
return await route.renderView(on: self) return await route.renderView(on: self)
case .user(let route): case .user(let route):
@@ -712,7 +712,7 @@ extension SiteRoute.View.UserRoute.Profile {
} }
} }
extension SiteRoute.View.QuickCalcRoute { extension SiteRoute.View.DuctulatorRoute {
func renderView( func renderView(
on request: ViewController.Request on request: ViewController.Request
@@ -722,7 +722,7 @@ extension SiteRoute.View.QuickCalcRoute {
switch self { switch self {
case .index: case .index:
return await request.view { return await request.view {
QuickCalcView( DuctulatorView(
isLoggedIn: request.isLoggedIn isLoggedIn: request.isLoggedIn
) )
} }
@@ -736,7 +736,7 @@ extension SiteRoute.View.QuickCalcRoute {
} }
return (ductSize, rectangularSize) return (ductSize, rectangularSize)
} onSuccess: { (ductSize, rectangularSize) in } onSuccess: { (ductSize, rectangularSize) in
QuickCalcView.Result(ductSize: ductSize, rectangularSize: rectangularSize) DuctulatorView.Result(ductSize: ductSize, rectangularSize: rectangularSize)
} }
} }
} }

View File

@@ -6,7 +6,7 @@ import ManualDClient
import ManualDCore import ManualDCore
import Styleguide import Styleguide
struct QuickCalcView: HTML, Sendable { struct DuctulatorView: HTML, Sendable {
let isLoggedIn: Bool let isLoggedIn: Bool
@@ -32,7 +32,7 @@ struct QuickCalcView: HTML, Sendable {
div(.class("flex space-x-6 items-center text-4xl")) { div(.class("flex space-x-6 items-center text-4xl")) {
SVG(.calculator) SVG(.calculator)
h1(.class("text-4xl font-bold me-10")) { h1(.class("text-4xl font-bold me-10")) {
"Duct Size" "Ductulator"
} }
} }
@@ -42,7 +42,7 @@ struct QuickCalcView: HTML, Sendable {
form( form(
.class("space-y-4 mt-6"), .class("space-y-4 mt-6"),
.hx.post(route: .quickCalc(.index)), .hx.post(route: .ductulator(.index)),
.hx.target("#\(Result.id)"), .hx.target("#\(Result.id)"),
.hx.swap(.outerHTML) .hx.swap(.outerHTML)
) { ) {
@@ -103,7 +103,7 @@ struct QuickCalcView: HTML, Sendable {
h2(.class("text-3xl font-bold")) { "Result" } h2(.class("text-3xl font-bold")) { "Result" }
button( button(
.class("btn btn-primary"), .class("btn btn-primary"),
.hx.get(route: .quickCalc(.index)), .hx.get(route: .ductulator(.index)),
.hx.target("body"), .hx.target("body"),
.hx.swap(.outerHTML) .hx.swap(.outerHTML)
) { ) {

View File

@@ -1,15 +1,19 @@
import Elementary import Elementary
import ElementaryHTMX import ElementaryHTMX
import Styleguide
struct HomeView: HTML, Sendable { struct HomeView: HTML, Sendable {
var body: some HTML { var body: some HTML {
div( // Uncomment to test different theme's. div( // Uncomment to test different theme's.
// .data("theme", value: "cyberpunk") // .data("theme", value: "cyberpunk")
// NOTE: Footer background color will follow system theme, it will actually be the // NOTE: Footer background color will follow system theme.
// same as the `hero` background in reality.
) { ) {
div(.class("flex justify-end m-4")) { div(.class("flex justify-end space-x-4 m-4")) {
DuctulatorButton()
.attributes(.class("btn-ghost btn-accent text-lg"))
.tooltip("Duct size calculator", position: .left)
button( button(
.class("btn btn-ghost btn-secondary text-lg"), .class("btn btn-ghost btn-secondary text-lg"),
.hx.get(route: .login(.index())), .hx.get(route: .login(.index())),
@@ -20,12 +24,13 @@ struct HomeView: HTML, Sendable {
"Login" "Login"
} }
} }
div(.class("hero")) {
div(.class("mx-10 lg:mx-20")) {
div( div(
.class( .class(
""" """
relative hero-content text-center bg-base-300 relative text-center bg-base-300
w-full min-h-[400px] rounded-3xl shadow-3xl overflow-hidden rounded-3xl shadow-3xl overflow-hidden
""" """
) )
) { ) {
@@ -62,7 +67,7 @@ struct HomeView: HTML, Sendable {
) { ) {
"Get Started" "Get Started"
} }
p(.class("text-xs italic mt-8")) { p(.class("text-xs italic my-6")) {
""" """
Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA).
@@ -72,9 +77,7 @@ struct HomeView: HTML, Sendable {
} }
} }
} div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 my-6")) {
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 mx-20 my-6")) {
div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) { div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) {
div(.class("flex items-center space-x-4")) { div(.class("flex items-center space-x-4")) {
div(.class("text-5xl text-primary font-bold")) { div(.class("text-5xl text-primary font-bold")) {
@@ -118,24 +121,26 @@ struct HomeView: HTML, Sendable {
} }
} }
} }
}
// TODO: When beta flag is gone, then remove the responsive margin of the header.
var header: some HTML<HTMLTag.div> { var header: some HTML<HTMLTag.div> {
div(.class("flex justify-center items-center")) { div(.class("flex justify-center mt-30 md:mt-15 lg:mt-6")) {
div( div(
.class( .class(
""" """
flex border-b-6 border-accent flex items-end border-b-6 border-accent
text-8xl font-bold my-auto space-2 text-8xl font-bold my-auto space-2
""" """
) )
) { ) {
h1(.class("me-2")) { "Duct Calc" } h1(.class("me-2")) { "Duct Calc" }
div(.class("")) { div {
span( span(
.class( .class(
""" """
bg-secondary rounded-md bg-secondary rounded-md
text-5xl rotate-180 p-2 text-5xl rotate-180 p-2 -mx-2
""" """
), ),
.style("writing-mode: vertical-rl") .style("writing-mode: vertical-rl")

View File

@@ -28,13 +28,13 @@ struct ViewControllerTests {
} }
@Test @Test
func quickCalc() async throws { func ductulator() async throws {
try await withDependencies { try await withDependencies {
$0.viewController = .liveValue $0.viewController = .liveValue
$0.auth = .failing $0.auth = .failing
} operation: { } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.viewController) var viewController
let view = try await viewController.view(.test(.quickCalc(.index))) let view = try await viewController.view(.test(.ductulator(.index)))
assertSnapshot(of: view, as: .html) assertSnapshot(of: view, as: .html)
} }
} }

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Duct Calc</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content="ductcalc.com" name="og:site_name">
<meta content="Duct Calc" name="og:title">
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
<meta content="/images/mand_logo.png" name="og:image">
<meta content="/images/mand_logo.png" name="twitter:image">
<meta content="Duct Calc" name="twitter:image:alt">
<meta content="summary_large_image" name="twitter:card">
<meta content="1536" name="og:image:width">
<meta content="1024" name="og:image:height">
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
<script src="/js/htmx-download.js"></script>
<script src="/js/main.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<link rel="stylesheet" href="/css/output.css">
<link rel="stylesheet" href="/css/htmx.css">
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
</head>
<body>
<div class="flex flex-col min-h-screen min-w-full justify-between" data-theme="default">
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
<div>
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
<div class="flex flex-1 space-x-4 items-center">
<div class="tooltip tooltip-right" data-tip="Home">
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/">
<img src="/images/mand_logo_sm.webp">
Duct Calc<span></span></a>
</div>
</div>
</nav>
<div class="flex justify-center items-center px-10">
<div class="bg-base-300 rounded-3xl shadow-3xl
p-6 w-full">
<div class="flex space-x-6 items-center text-4xl">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calculator-icon lucide-calculator"><rect width="16" height="20" x="4" y="2" rx="2"/><line x1="8" x2="16" y1="6" y2="6"/><line x1="16" x2="16" y1="14" y2="18"/><path d="M16 10h.01"/><path d="M12 10h.01"/><path d="M8 10h.01"/><path d="M12 14h.01"/><path d="M8 14h.01"/><path d="M12 18h.01"/><path d="M8 18h.01"/></svg>
<h1 class="text-4xl font-bold me-10">Ductulator</h1>
</div>
<p class="text-primary font-bold italic">Calculate duct size for the given parameters</p>
<form class="space-y-4 mt-6" hx-post="/duct-size" hx-target="#resultView" hx-swap="outerHTML">
<label class="input w-full"><span class="label">CFM</span>
<input name="cfm" type="number" placeholder="1000" required autofocus>
Friction Rate</label><label class="input w-full"><span class="label"></span>
<input name="frictionRate" value="0.06" required type="number" min="0.01" step="0.01">
Height</label><label class="input w-full"><span class="label"></span>
<input name="height" type="number" placeholder="Height (Optional)"></label>
<button class="btn btn-secondary btn-block mt-6" type="submit">Submit</button>
</form>
<div id="resultView"></div>
</div>
</div>
</div>
</main>
<div class="bottom-0 left-0 bg-error">
<footer class="footer sm:footer-horizontal footer-center
bg-base-300 text-base-content p-4">
<aside>
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
Openly licensed via CC-BY-NC-SA 4.0<a class="btn btn-ghost" href="https://git.housh.dev/michael/swift-duct-calc/src/branch/main/LICENSE" target="_blank"></a>
</aside>
</footer>
</div>
</div>
</body>
</html>

View File

@@ -32,38 +32,38 @@
<div class="flex flex-col min-h-screen min-w-full justify-between" data-theme="default"> <div class="flex flex-col min-h-screen min-w-full justify-between" data-theme="default">
<main class="flex flex-col min-h-screen min-w-full grow mb-auto"> <main class="flex flex-col min-h-screen min-w-full grow mb-auto">
<div> <div>
<div class="flex justify-end m-4"> <div class="flex justify-end space-x-4 m-4">
<div class="tooltip tooltip-left" data-tip="Duct size calculator"><a class="btn btn-ghost btn-accent text-lg" href="/duct-size" target="_blank">Ductulator</a></div>
<button class="btn btn-ghost btn-secondary text-lg" hx-get="/login" hx-target="body" hx-swap="outerHTML" hx-push-url="true">Login</button> <button class="btn btn-ghost btn-secondary text-lg" hx-get="/login" hx-target="body" hx-swap="outerHTML" hx-push-url="true">Login</button>
</div> </div>
<div class="hero"> <div class="mx-10 lg:mx-20">
<div class="relative hero-content text-center bg-base-300 <div class="relative text-center bg-base-300
w-full min-h-[400px] rounded-3xl shadow-3xl overflow-hidden"> rounded-3xl shadow-3xl overflow-hidden">
<div class="bg-secondary text-xl font-bold <div class="bg-secondary text-xl font-bold
absolute top-10 -left-15 absolute top-10 -left-15
px-6 py-2 w-[250px] -rotate-45">BETA</div> px-6 py-2 w-[250px] -rotate-45">BETA</div>
<div> <div>
<div class="flex justify-center items-center"> <div class="flex justify-center mt-30 md:mt-15 lg:mt-6">
<div class="flex border-b-6 border-accent <div class="flex items-end border-b-6 border-accent
text-8xl font-bold my-auto space-2"> text-8xl font-bold my-auto space-2">
<h1 class="me-2">Duct Calc</h1> <h1 class="me-2">Duct Calc</h1>
<div class=""> <div>
<span class="bg-secondary rounded-md <span class="bg-secondary rounded-md
text-5xl rotate-180 p-2" style="writing-mode: vertical-rl">Pro</span> text-5xl rotate-180 p-2 -mx-2" style="writing-mode: vertical-rl">Pro</span>
</div> </div>
</div> </div>
</div> </div>
Open source residential duct design program<a class="btn btn-ghost text-md text-primary font-bold italic" href="https://git.housh.dev/michael/swift-duct-calc" target="_blank"></a> Open source residential duct design program<a class="btn btn-ghost text-md text-primary font-bold italic" href="https://git.housh.dev/michael/swift-duct-calc" target="_blank"></a>
<p class="text-3xl py-6">Manual-D™ speed sheet, but on the web!</p> <p class="text-3xl py-6">Manual-D™ speed sheet, but on the web!</p>
<button class="btn btn-xl btn-primary mt-6" hx-get="/signup" hx-target="body" hx-swap="outerHTML">Get Started</button> <button class="btn btn-xl btn-primary mt-6" hx-get="/signup" hx-target="body" hx-swap="outerHTML">Get Started</button>
<p class="text-xs italic mt-8"> <p class="text-xs italic my-6">
Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA).
This site is not designed by or affiliated with ACCA. This site is not designed by or affiliated with ACCA.
</p> </p>
</div> </div>
</div> </div>
</div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mx-20 my-6">
<div class="border-3 border-accent rounded-lg shadow-lg p-4"> <div class="border-3 border-accent rounded-lg shadow-lg p-4">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<div class="text-5xl text-primary font-bold">Features</div> <div class="text-5xl text-primary font-bold">Features</div>
@@ -97,6 +97,7 @@ text-8xl font-bold my-auto space-2">
</div> </div>
</div> </div>
</div> </div>
</div>
</main> </main>
<div class="bottom-0 left-0 bg-error"> <div class="bottom-0 left-0 bg-error">
<footer class="footer sm:footer-horizontal footer-center <footer class="footer sm:footer-horizontal footer-center