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:
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
) {
|
) {
|
||||||
@@ -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,46 +77,45 @@ struct HomeView: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 my-6")) {
|
||||||
|
div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) {
|
||||||
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 mx-20 my-6")) {
|
div(.class("flex items-center space-x-4")) {
|
||||||
div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) {
|
div(.class("text-5xl text-primary font-bold")) {
|
||||||
div(.class("flex items-center space-x-4")) {
|
"Features"
|
||||||
div(.class("text-5xl text-primary font-bold")) {
|
}
|
||||||
"Features"
|
}
|
||||||
}
|
div(.class("text-xl ms-10 mt-10")) {
|
||||||
}
|
ul(.class("list-disc")) {
|
||||||
div(.class("text-xl ms-10 mt-10")) {
|
li {
|
||||||
ul(.class("list-disc")) {
|
div(
|
||||||
li {
|
.class("font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit")
|
||||||
div(
|
) {
|
||||||
.class("font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit")
|
"Built by humans"
|
||||||
) {
|
}
|
||||||
"Built by humans"
|
}
|
||||||
}
|
li { "Fully open source." }
|
||||||
|
li { "Great replacement for speed sheet users." }
|
||||||
|
li { "Great for classrooms." }
|
||||||
|
li { "Store your projects in one place." }
|
||||||
|
li { "Export final project to pdf." }
|
||||||
|
li { "Import room loads via CSV file." }
|
||||||
|
li { "Web based." }
|
||||||
|
li { "Self host (run on your own infrastructure)." }
|
||||||
}
|
}
|
||||||
li { "Fully open source." }
|
|
||||||
li { "Great replacement for speed sheet users." }
|
|
||||||
li { "Great for classrooms." }
|
|
||||||
li { "Store your projects in one place." }
|
|
||||||
li { "Export final project to pdf." }
|
|
||||||
li { "Import room loads via CSV file." }
|
|
||||||
li { "Web based." }
|
|
||||||
li { "Self host (run on your own infrastructure)." }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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("text-5xl text-primary font-bold")) {
|
div(.class("text-5xl text-primary font-bold")) {
|
||||||
"Coming Soon"
|
"Coming Soon"
|
||||||
}
|
}
|
||||||
div(.class("text-xl ms-10 mt-10")) {
|
div(.class("text-xl ms-10 mt-10")) {
|
||||||
ul(.class("list-disc")) {
|
ul(.class("list-disc")) {
|
||||||
li { "API integration." }
|
li { "API integration." }
|
||||||
li { "Command line interface." }
|
li { "Command line interface." }
|
||||||
li { "Fitting selection tool." }
|
li { "Fitting selection tool." }
|
||||||
li { "Room load import from PDF." }
|
li { "Room load import from PDF." }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,23 +123,24 @@ 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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -32,67 +32,68 @@
|
|||||||
<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>
|
</div>
|
||||||
|
<div class="text-xl ms-10 mt-10">
|
||||||
|
<ul class="list-disc">
|
||||||
|
<li>
|
||||||
|
<div class="font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit">Built by humans</div>
|
||||||
|
</li>
|
||||||
|
<li>Fully open source.</li>
|
||||||
|
<li>Great replacement for speed sheet users.</li>
|
||||||
|
<li>Great for classrooms.</li>
|
||||||
|
<li>Store your projects in one place.</li>
|
||||||
|
<li>Export final project to pdf.</li>
|
||||||
|
<li>Import room loads via CSV file.</li>
|
||||||
|
<li>Web based.</li>
|
||||||
|
<li>Self host (run on your own infrastructure).</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xl ms-10 mt-10">
|
<div class="border-3 border-accent rounded-lg shadow-lg p-4">
|
||||||
<ul class="list-disc">
|
<div class="text-5xl text-primary font-bold">Coming Soon</div>
|
||||||
<li>
|
<div class="text-xl ms-10 mt-10">
|
||||||
<div class="font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit">Built by humans</div>
|
<ul class="list-disc">
|
||||||
</li>
|
<li>API integration.</li>
|
||||||
<li>Fully open source.</li>
|
<li>Command line interface.</li>
|
||||||
<li>Great replacement for speed sheet users.</li>
|
<li>Fitting selection tool.</li>
|
||||||
<li>Great for classrooms.</li>
|
<li>Room load import from PDF.</li>
|
||||||
<li>Store your projects in one place.</li>
|
</ul>
|
||||||
<li>Export final project to pdf.</li>
|
</div>
|
||||||
<li>Import room loads via CSV file.</li>
|
|
||||||
<li>Web based.</li>
|
|
||||||
<li>Self host (run on your own infrastructure).</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="border-3 border-accent rounded-lg shadow-lg p-4">
|
|
||||||
<div class="text-5xl text-primary font-bold">Coming Soon</div>
|
|
||||||
<div class="text-xl ms-10 mt-10">
|
|
||||||
<ul class="list-disc">
|
|
||||||
<li>API integration.</li>
|
|
||||||
<li>Command line interface.</li>
|
|
||||||
<li>Fitting selection tool.</li>
|
|
||||||
<li>Room load import from PDF.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user