From 95ac55019144e277266f2de93da132bfd812907a Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Thu, 16 Jul 2026 07:34:45 -0400 Subject: [PATCH] fix: update to correct NeoBrutalismCSS CDN and class names - Switch CDN to jsdelivr (matches design spec) - Use nb-card, nb-card-title, nb-button default, nb-checkbox, nb-navbar-link - Add Google Fonts import for Lexend Mega - Style all route links as nb-button default buttons - Fix navbar to use nb-navbar-brand and nb-navbar-nav - Update style.css for new NB version compatibility --- frontend/static/style.css | 29 +++++++++++--------- src/Sis/Page/Activity.hs | 8 +++--- src/Sis/Page/Chores.hs | 10 +++---- src/Sis/Page/Dashboard.hs | 14 +++++----- src/Sis/Page/Household.hs | 16 +++++------ src/Sis/Page/Login.hs | 17 ++++++------ src/Sis/Page/Signup.hs | 16 +++++------ src/Sis/Style.hs | 58 +++++++++++++++++++++------------------ src/Sis/View/Layout.hs | 15 ++++++---- 9 files changed, 98 insertions(+), 85 deletions(-) diff --git a/frontend/static/style.css b/frontend/static/style.css index 1e71885..075507f 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -16,42 +16,45 @@ body { padding: 0; } +/* Override NB navbar to have drop shadow like our design */ .nb-navbar { - background: #fff; - border-bottom: 3px solid #000; - padding: 0.75rem 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; box-shadow: 4px 4px 0 #000; margin-bottom: 2rem; } -.nb-navbar-start, .nb-navbar-end { - display: flex; - align-items: center; - gap: 0.5rem; -} - +/* List items with subtle dividers */ .nb-list-item { border-bottom: 1px solid rgba(0,0,0,0.1); + padding: 0.5rem; } .nb-list-item:last-child { border-bottom: none; } +/* Stat tiles */ +.stat-tile { + text-align: center; + padding: 1rem; +} + +/* Container padding */ +.nb-container { + padding: 0 1rem; +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +/* Responsive */ @media (max-width: 768px) { .nb-navbar { flex-direction: column; gap: 0.5rem; padding: 0.5rem; } - .nb-navbar-end { + .nb-navbar-nav { flex-wrap: wrap; justify-content: center; } diff --git a/src/Sis/Page/Activity.hs b/src/Sis/Page/Activity.hs index 75dabad..f61b452 100644 --- a/src/Sis/Page/Activity.hs +++ b/src/Sis/Page/Activity.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -52,7 +52,7 @@ instance (DB :> es, IOE :> es) => HyperView ActivityPage es where activityView :: ActivityLogPage -> View ActivityPage () activityView log = do el @ att "class" nbContainerClass @ att "style" "max-width:960px;margin:0 auto" $ do - el @ att "class" nbFontHeading1Class $ text "Activity Log" + el @ att "class" nbHeadingClass $ text "Activity Log" if null (alpEntries log) then el @ att "style" "opacity:0.5" $ text "No activity recorded yet." else el @ att "class" nbBoxClass $ mapM_ entryRow (alpEntries log) @@ -60,12 +60,12 @@ activityView log = do then el @ att "style" "margin-top:1rem;display:flex;gap:0.5rem;justify-content:center" $ do let totalPages = (alpTotal log + alpPerPage log - 1) `div` alpPerPage log if alpPage log > 1 - then button (GoToPage (alpPage log - 1)) @ att "class" nbButtonClass $ text "Previous" + then button (GoToPage (alpPage log - 1)) @ att "class" nbButtonDefaultClass $ text "Previous" else none el @ att "style" "align-self:center" $ text ("Page " <> T.pack (show (alpPage log)) <> " of " <> T.pack (show totalPages)) if alpPage log < totalPages - then button (GoToPage (alpPage log + 1)) @ att "class" nbButtonClass $ text "Next" + then button (GoToPage (alpPage log + 1)) @ att "class" nbButtonDefaultClass $ text "Next" else none else none diff --git a/src/Sis/Page/Chores.hs b/src/Sis/Page/Chores.hs index ef84924..61d819e 100644 --- a/src/Sis/Page/Chores.hs +++ b/src/Sis/Page/Chores.hs @@ -18,7 +18,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -59,8 +59,8 @@ choresView :: [Chore] -> View ChoresPage () choresView chores' = do el @ att "class" nbContainerClass @ att "style" "max-width:960px;margin:0 auto" $ do el @ att "style" "display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem" $ do - el @ att "class" nbFontHeading1Class $ text "Chores" - button CNewChore @ att "class" nbButtonClass $ text "+ New Chore" + el @ att "class" nbHeadingClass $ text "Chores" + button CNewChore @ att "class" nbButtonDefaultClass $ text "+ New Chore" if null chores' then el @ att "style" "opacity:0.5" $ text "No chores yet. Create one to get started!" else el @ att "class" nbBoxClass $ mapM_ choreRow chores' @@ -73,8 +73,8 @@ choreRow c = do el @ att "style" "font-weight:500" $ text (choreName c) el @ att "style" "opacity:0.5;font-size:0.85rem" $ text (scheduleLabel (choreSchedule c)) el @ att "style" "display:flex;gap:0.25rem" $ do - button CNewChore @ att "class" nbButtonClass @ att "style" "font-size:0.8rem;padding:0.25rem 0.5rem" $ text "Edit" - button (CDeleteChore (choreId c)) @ att "class" nbButtonClass @ att "style" "font-size:0.8rem;padding:0.25rem 0.5rem;background:var(--nb-red)" $ text "Delete" + button CNewChore @ att "class" nbButtonDefaultClass @ att "style" "font-size:0.8rem;padding:0.25rem 0.5rem" $ text "Edit" + button (CDeleteChore (choreId c)) @ att "class" nbButtonDefaultClass @ att "style" "font-size:0.8rem;padding:0.25rem 0.5rem;background:var(--nb-red)" $ text "Delete" scheduleBadge :: Schedule -> Text scheduleBadge ScheduleSometime{} = "sometime" diff --git a/src/Sis/Page/Dashboard.hs b/src/Sis/Page/Dashboard.hs index 7a1bfee..bcc9333 100644 --- a/src/Sis/Page/Dashboard.hs +++ b/src/Sis/Page/Dashboard.hs @@ -19,7 +19,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -56,27 +56,27 @@ instance (DB :> es, IOE :> es) => HyperView DashboardPage es where dashboardView :: Dashboard -> View DashboardPage () dashboardView dash = do el @ att "class" nbContainerClass @ att "style" "max-width:960px;margin:0 auto" $ do - el @ att "class" nbFontHeading1Class $ text "Dashboard" + el @ att "class" nbHeadingClass $ text "Dashboard" el @ att "style" "display:flex;gap:1rem;margin-bottom:1.5rem" $ do let stats = dashStats dash statTile "Overdue" (T.pack (show (dsOverdue stats))) colorRed statTile "Due Today" (T.pack (show (dsDueToday stats))) colorYellow statTile "Done This Week" (T.pack (show (dsDoneThisWeek stats))) colorGreen - el @ att "class" nbFontHeading2Class $ text "Overdue & Due Today" + el @ att "class" nbHeadingClass $ text "Overdue & Due Today" if null (dashDueItems dash) then el @ att "style" "opacity:0.5" $ text "Nothing due! Great job." else el @ att "class" nbBoxClass $ do mapM_ dueItemRow (dashDueItems dash) - el @ att "class" nbFontHeading2Class $ text "Completed Today" + el @ att "class" nbHeadingClass $ text "Completed Today" if null (dashCompletedItems dash) then el @ att "style" "opacity:0.5" $ text "No activity recorded today." else el @ att "class" nbBoxClass $ mapM_ completedItemRow (dashCompletedItems dash) - route RActivity $ text "View Full Activity Log" + route RActivity @ att "class" nbButtonDefaultClass $ text "View Full Activity Log" statTile :: Text -> Text -> Text -> View ctx () statTile label count color = do el @ att "class" nbBoxClass @ att "style" ("flex:1;text-align:center;padding:1rem;border-color:" <> color) $ do - el @ att "class" nbFontHeading1Class $ text count + el @ att "class" nbHeadingClass $ text count text label dueItemRow :: DueItem -> View DashboardPage () @@ -90,7 +90,7 @@ dueItemRow di = do case diAssigneeName di of Just name -> el @ att "style" "opacity:0.5" $ text ("(" <> name <> ")") Nothing -> none - button (CheckOff (occurrenceId (diOccurrence di))) @ att "class" nbButtonClass @ att "style" "font-size:0.85rem" $ text "Check Off" + button (CheckOff (occurrenceId (diOccurrence di))) @ att "class" nbButtonDefaultClass @ att "style" "font-size:0.85rem" $ text "Check Off" completedItemRow :: CompletedItem -> View DashboardPage () completedItemRow ci = do diff --git a/src/Sis/Page/Household.hs b/src/Sis/Page/Household.hs index 5fe4526..ca1a7e8 100644 --- a/src/Sis/Page/Household.hs +++ b/src/Sis/Page/Household.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -69,7 +69,7 @@ noHouseholdView :: View HouseholdPage () noHouseholdView = do el @ att "class" nbContainerClass @ att "style" "max-width:480px;margin:4rem auto" $ do el @ att "class" nbBoxClass @ att "style" "padding:2rem;text-align:center" $ do - el @ att "class" nbFontHeading1Class $ text "Create Your Household" + el @ att "class" nbHeadingClass $ text "Create Your Household" el @ att "style" "margin-bottom:1rem" $ text "You need a household to get started." -- Form for household creation would go here el $ text "Household creation form coming soon" @@ -77,18 +77,18 @@ noHouseholdView = do householdView :: Household -> [Membership] -> [Invite] -> View HouseholdPage () householdView h mems invs = do el @ att "class" nbContainerClass @ att "style" "max-width:960px;margin:0 auto" $ do - el @ att "class" nbFontHeading1Class $ text (householdName h) + el @ att "class" nbHeadingClass $ text (householdName h) el @ att "style" "opacity:0.7" $ text (T.pack (show (length mems)) <> " members") - el @ att "class" nbFontHeading2Class $ text "Members" + el @ att "class" nbHeadingClass $ text "Members" el @ att "class" nbBoxClass @ att "style" "margin-bottom:1.5rem" $ mapM_ memberRow mems - el @ att "class" nbFontHeading2Class $ text "Invite Members" + el @ att "class" nbHeadingClass $ text "Invite Members" el @ att "class" nbBoxClass $ do el @ att "style" "margin-bottom:0.5rem" $ text "Create an invite link to share:" - button CreateInviteAction @ att "class" nbButtonClass $ text "+ Create Invite Link" + button CreateInviteAction @ att "class" nbButtonDefaultClass $ text "+ Create Invite Link" if null invs then none else el @ att "style" "margin-top:1rem" $ do - el @ att "class" nbFontHeading2Class $ text "Pending Invites" + el @ att "class" nbHeadingClass $ text "Pending Invites" mapM_ inviteRow (filter ((== InvitePending) . inviteStatus) invs) memberRow :: Membership -> View HouseholdPage () @@ -106,7 +106,7 @@ inviteRow :: Invite -> View HouseholdPage () inviteRow i = do el @ att "class" nbListItemClass @ att "style" "display:flex;justify-content:space-between;padding:0.5rem" $ do el @ att "style" "font-size:0.85rem" $ text ("/invite/" <> inviteCode i) - button (RevokeInviteAction (inviteId i)) @ att "class" nbButtonClass @ att "style" "font-size:0.8rem;background:var(--nb-red)" $ + button (RevokeInviteAction (inviteId i)) @ att "class" nbButtonDefaultClass @ att "style" "font-size:0.8rem;background:var(--nb-red)" $ text "Revoke" initials :: Text -> Text diff --git a/src/Sis/Page/Login.hs b/src/Sis/Page/Login.hs index fc5bb58..c024fdc 100644 --- a/src/Sis/Page/Login.hs +++ b/src/Sis/Page/Login.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -49,15 +49,15 @@ loginSuccessView :: View LoginPage () loginSuccessView = do el @ att "class" nbContainerClass @ att "style" "max-width:480px;margin:4rem auto;text-align:center" $ do el @ att "class" nbBoxClass @ att "style" "padding:2rem" $ do - el @ att "class" nbFontHeading1Class $ text "Logged In!" + el @ att "class" nbHeadingClass $ text "Logged In!" el @ att "style" "margin-top:1rem;margin-bottom:1rem" $ text "You are now logged in." - route RDashboard $ text "Go to Dashboard" + route RDashboard @ att "class" nbButtonDefaultClass $ text "Go to Dashboard" loginView :: Maybe Text -> View LoginPage () loginView mError = do el @ att "class" nbContainerClass @ att "style" "max-width:480px;margin:4rem auto" $ do el @ att "class" nbBoxClass @ att "style" "padding:2rem" $ do - el @ att "class" nbFontHeading1Class $ text "Welcome Back" + el @ att "class" nbHeadingClass $ text "Welcome Back" el @ att "style" "opacity:0.7;margin-bottom:1.5rem" $ text "Log in to manage your household chores." case mError of Just err -> @@ -69,10 +69,11 @@ loginView mError = do el @ att "class" nbLabelClass $ text "Password" tag "input" @ att "type" "password" . att "name" "lfPassword" . att "class" nbInputClass @ att "style" "width:100%" $ none el @ att "style" "display:flex;align-items:center;gap:0.5rem;margin-bottom:1rem" $ do - tag "input" @ att "type" "checkbox" . att "name" "lfRemember" $ none - text "Remember me" - submit (text "Log In") @ att "class" nbButtonClass @ att "style" "width:100%" - route RSignup $ text "Don't have an account? Sign Up" + tag "input" @ att "type" "checkbox" . att "name" "lfRemember" . att "class" "nb-checkbox" $ none + el @ att "class" nbLabelClass $ text "Remember me" + submit (text "Log In") @ att "class" nbButtonDefaultClass @ att "style" "width:100%" + el @ att "style" "margin-top:1rem;text-align:center" $ do + route RSignup @ att "class" nbButtonDefaultClass $ text "Sign Up" page :: (Hyperbole :> es, DB :> es, IOE :> es) => Page es '[LoginPage] page = do mSession <- lookupSession @UserSession diff --git a/src/Sis/Page/Signup.hs b/src/Sis/Page/Signup.hs index 16517b1..ed144cb 100644 --- a/src/Sis/Page/Signup.hs +++ b/src/Sis/Page/Signup.hs @@ -18,7 +18,7 @@ import Effectful import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Database import Sis.Route -import Sis.Style +import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -55,15 +55,15 @@ signupSuccessView :: View SignupPage () signupSuccessView = do el @ att "class" nbContainerClass @ att "style" "max-width:480px;margin:4rem auto;text-align:center" $ do el @ att "class" nbBoxClass @ att "style" "padding:2rem" $ do - el @ att "class" nbFontHeading1Class $ text "Account Created!" + el @ att "class" nbHeadingClass $ text "Account Created!" el @ att "style" "margin-top:1rem;margin-bottom:1rem" $ text "Your account has been created." - route RDashboard $ text "Go to Dashboard" + route RDashboard @ att "class" nbButtonDefaultClass $ text "Go to Dashboard" signupView :: Maybe Text -> View SignupPage () signupView mError = do el @ att "class" nbContainerClass @ att "style" "max-width:480px;margin:4rem auto" $ do el @ att "class" nbBoxClass @ att "style" "padding:2rem" $ do - el @ att "class" nbFontHeading1Class $ text "Create Account" + el @ att "class" nbHeadingClass $ text "Create Account" el @ att "style" "opacity:0.7;margin-bottom:1.5rem" $ text "Join your household chore tracker." case mError of Just err -> @@ -79,10 +79,10 @@ signupView mError = do el @ att "class" nbLabelClass $ text "Confirm Password" tag "input" @ att "type" "password" . att "name" "sfConfirm" . att "class" nbInputClass @ att "style" "width:100%" $ none el @ att "style" "display:flex;align-items:center;gap:0.5rem;margin-bottom:1rem" $ do - tag "input" @ att "type" "checkbox" . att "name" "sfAgree" $ none - text "I agree to the terms of service" - submit (text "Sign Up") @ att "class" nbButtonClass @ att "style" "width:100%" - route RLogin $ text "Already have an account? Log In" + tag "input" @ att "type" "checkbox" . att "name" "sfAgree" . att "class" "nb-checkbox" $ none + el @ att "class" nbLabelClass $ text "I agree to the terms of service" + submit (text "Sign Up") @ att "class" nbButtonDefaultClass @ att "style" "width:100%" + route RLogin @ att "class" nbButtonDefaultClass $ text "Log In" page :: (Hyperbole :> es, DB :> es, IOE :> es) => Page es '[SignupPage] page = do mSession <- lookupSession @UserSession diff --git a/src/Sis/Style.hs b/src/Sis/Style.hs index b93531c..8182259 100644 --- a/src/Sis/Style.hs +++ b/src/Sis/Style.hs @@ -1,42 +1,48 @@ {-# LANGUAGE OverloadedStrings #-} -{- | Neo Brutalism CSS class name constants for Hyperbole views. -Use with: \@ att \"class\" nbBoxClass --} -module Sis.Style ( - nbBoxClass, - nbButtonClass, - nbInputClass, - nbLabelClass, - nbBadgeClass, - nbFontHeading1Class, - nbFontHeading2Class, - nbNavbarClass, - nbContainerClass, - nbListItemClass, - colorRed, - colorYellow, - colorGreen, -) where +-- | Neo Brutalism CSS class name constants for Hyperbole views. +-- Based on https://github.com/matifandy8/NeoBrutalismCSS +module Sis.Style + ( nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass + , nbBadgeClass, nbHeadingClass + , nbNavbarClass, nbNavbarLinkClass, nbContainerClass, nbListItemClass + , colorRed, colorYellow, colorGreen + ) where import Data.Text (Text) -nbBoxClass, nbButtonClass, nbInputClass, nbLabelClass, nbBadgeClass :: Text -nbBoxClass = "nb-box" -nbButtonClass = "nb-button" +-- Box/panel +nbBoxClass :: Text +nbBoxClass = "nb-card" + +-- Button (default variant: black border, white bg) +nbButtonDefaultClass :: Text +nbButtonDefaultClass = "nb-button default" + +-- Form elements +nbInputClass, nbLabelClass :: Text nbInputClass = "nb-input" nbLabelClass = "nb-label" -nbBadgeClass = "nb-badge" -nbFontHeading1Class, nbFontHeading2Class :: Text -nbFontHeading1Class = "nb-font-heading1" -nbFontHeading2Class = "nb-font-heading2" +-- Badge/pill (inline label) +nbBadgeClass :: Text +nbBadgeClass = "nb-button default" -nbNavbarClass, nbContainerClass, nbListItemClass :: Text +-- Heading +nbHeadingClass :: Text +nbHeadingClass = "nb-card-title" + +-- Navbar +nbNavbarClass, nbNavbarLinkClass :: Text nbNavbarClass = "nb-navbar" +nbNavbarLinkClass = "nb-navbar-link" + +-- Layout +nbContainerClass, nbListItemClass :: Text nbContainerClass = "nb-container" nbListItemClass = "nb-list-item" +-- CSS color variables colorRed, colorYellow, colorGreen :: Text colorRed = "var(--nb-red)" colorYellow = "var(--nb-yellow)" diff --git a/src/Sis/View/Layout.hs b/src/Sis/View/Layout.hs index e512f04..a107f7d 100644 --- a/src/Sis/View/Layout.hs +++ b/src/Sis/View/Layout.hs @@ -15,7 +15,7 @@ import Data.Text (Text) import GHC.Generics (Generic) import Sis.Route -import Sis.Style +import Sis.Style (nbNavbarClass, nbNavbarLinkClass, nbHeadingClass, nbButtonDefaultClass) import Web.Hyperbole import Web.Hyperbole.Effect.Session @@ -44,8 +44,12 @@ documentHead = do title "Sis — Household Chore Tracker" mobileFriendly meta @ att "charset" "UTF-8" - stylesheet "https://unpkg.com/neobrutalismcss@latest" + stylesheet "https://cdn.jsdelivr.net/gh/matifandy8/NeoBrutalismCSS/dist/index.min.css" stylesheet "/static/style.css" + -- Lexend Mega font for headings/buttons (used by NeoBrutalismCSS) + tag "link" @ att "rel" "preconnect" . att "href" "https://fonts.googleapis.com" $ none + tag "link" @ att "rel" "preconnect" . att "href" "https://fonts.gstatic.com" @ att "crossorigin" "" $ none + tag "link" @ att "href" "https://fonts.googleapis.com/css2?family=Lexend+Mega:wght@900&family=Public+Sans:wght@400;600;700&display=swap" . att "rel" "stylesheet" $ none tag "link" @ att "rel" "manifest" . att "href" "/static/manifest.json" $ none script' scriptEmbed @@ -56,9 +60,8 @@ documentHead = do navbar :: View ctx () navbar = do el @ att "class" nbNavbarClass $ do - el @ att "class" "nb-navbar-start" $ do - el @ att "class" nbFontHeading2Class @ att "style" "font-weight:700" $ text "Sis" - el @ att "class" "nb-navbar-end" $ do + el @ att "class" "nb-navbar-brand" @ att "style" "font-weight:700" $ text "Sis" + el @ att "class" "nb-navbar-nav" $ do routeLink RDashboard "Dashboard" routeLink RChores "Chores" routeLink RHousehold "Household" @@ -67,4 +70,4 @@ navbar = do routeLink :: (Route r) => r -> Text -> View ctx () routeLink rt lbl = do - el @ att "class" nbButtonClass $ route rt $ text lbl + el @ att "class" nbNavbarLinkClass $ route rt $ text lbl