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
This commit is contained in:
2026-07-16 07:34:45 -04:00
parent e8036bab11
commit 95ac550191
9 changed files with 98 additions and 85 deletions
+16 -13
View File
@@ -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;
}
+4 -4
View File
@@ -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
+5 -5
View File
@@ -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"
+7 -7
View File
@@ -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
+8 -8
View File
@@ -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
+9 -8
View File
@@ -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
+8 -8
View File
@@ -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
+32 -26
View File
@@ -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)"
+9 -6
View File
@@ -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