Skip to content

Commit 33cc49e

Browse files
jasdumasclaude
andcommitted
Major UX improvements to Capital Campaign Forecaster
- Changed title to dark blue (#2c3e50) to match other apps - Made explainer button-style and clickable with gradient background, hover effects, and up/down arrows - Added info icon tooltips (?) to all input labels with helpful context - Fixed metric cards to display in ONE row using fluidRow with 4 columns (width=3 each) - Improved overall visual consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dd6046b commit 33cc49e

File tree

1 file changed

+126
-77
lines changed
  • capital-campaign-forecaster

1 file changed

+126
-77
lines changed

capital-campaign-forecaster/app.R

Lines changed: 126 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ ui <- fluidPage(
3535
3636
h2 {
3737
text-align: center;
38-
background: linear-gradient(135deg, #D68A93, #AD92B1);
39-
-webkit-background-clip: text;
40-
-webkit-text-fill-color: transparent;
41-
background-clip: text;
38+
color: #2c3e50;
4239
font-weight: 700;
4340
font-size: 2rem;
4441
margin-bottom: 0.3rem;
@@ -194,12 +191,45 @@ ui <- fluidPage(
194191
cursor: help;
195192
}
196193
194+
details summary {
195+
background: linear-gradient(135deg, #D68A93, #AD92B1);
196+
color: white;
197+
padding: 1rem 1.5rem;
198+
border-radius: 12px;
199+
cursor: pointer;
200+
font-size: 1.5rem;
201+
font-weight: 600;
202+
margin-bottom: 1.5rem;
203+
box-shadow: 0 4px 15px rgba(214, 138, 147, 0.3);
204+
transition: all 0.3s ease;
205+
list-style: none;
206+
}
207+
208+
details summary::-webkit-details-marker {
209+
display: none;
210+
}
211+
212+
details summary:hover {
213+
transform: translateY(-2px);
214+
box-shadow: 0 6px 20px rgba(214, 138, 147, 0.4);
215+
}
216+
217+
details summary::after {
218+
content: ' \\\\25BC';
219+
font-size: 0.8em;
220+
margin-left: 0.5rem;
221+
}
222+
223+
details[open] summary::after {
224+
content: ' \\\\25B2';
225+
}
226+
197227
.explainer-box {
198228
background: linear-gradient(135deg, rgba(249, 179, 151, 0.1) 0%, rgba(214, 138, 147, 0.1) 100%);
199229
border: 2px solid #F9B397;
200230
border-radius: 12px;
201-
padding: 1rem;
202-
margin: 1rem 0;
231+
padding: 1.5rem;
232+
margin: 0 0 1.5rem 0;
203233
}
204234
205235
.explainer-title {
@@ -283,10 +313,7 @@ ui <- fluidPage(
283313

284314
# Monte Carlo Explainer (Collapsible)
285315
tags$details(
286-
tags$summary(
287-
style = "cursor: pointer; font-size: 1.5rem; font-weight: 600; color: #D68A93; margin-bottom: 1rem;",
288-
"🎲 How This Tool Works: Monte Carlo Simulation"
289-
),
316+
tags$summary("🎲 How This Tool Works: Monte Carlo Simulation"),
290317
div(class = "explainer-box",
291318
p("Unlike simple forecasts that show one possible outcome, this tool runs ", strong("1,000 different simulations"),
292319
" to account for real-world uncertainty in fundraising."),
@@ -317,58 +344,72 @@ ui <- fluidPage(
317344
# Current State Inputs
318345
div(class = "section-header", style = "font-size: 1.2rem; margin-top: 0;", "📊 Current State"),
319346

320-
numericInput("current_donors",
321-
"Total Donors",
322-
value = 500,
323-
min = 10,
324-
step = 10),
325-
326-
sliderInput("current_retention",
327-
"Retention Rate",
328-
min = 20,
329-
max = 90,
330-
value = 45,
331-
step = 5,
332-
post = "%"),
333-
334-
numericInput("avg_gift",
335-
"Avg Gift ($)",
336-
value = 250,
337-
min = 10,
338-
step = 10),
339-
340-
sliderInput("baseline_acquisition",
341-
"New Donors/Year",
342-
min = 0,
343-
max = 500,
344-
value = 100,
345-
step = 10),
346-
347-
sliderInput("forecast_years",
348-
"Forecast Years",
349-
min = 1,
350-
max = 5,
351-
value = 3,
352-
step = 1),
347+
div(
348+
numericInput("current_donors",
349+
HTML("Total Donors <span class='help-icon' title='Active donors who gave in the past year'>?</span>"),
350+
value = 500,
351+
min = 10,
352+
step = 10)
353+
),
354+
355+
div(
356+
sliderInput("current_retention",
357+
HTML("Retention Rate <span class='help-icon' title='% of donors who give again next year (nonprofit avg: 45%)'>?</span>"),
358+
min = 20,
359+
max = 90,
360+
value = 45,
361+
step = 5,
362+
post = "%")
363+
),
364+
365+
div(
366+
numericInput("avg_gift",
367+
HTML("Avg Gift ($) <span class='help-icon' title='Typical donation amount across all donors'>?</span>"),
368+
value = 250,
369+
min = 10,
370+
step = 10)
371+
),
372+
373+
div(
374+
sliderInput("baseline_acquisition",
375+
HTML("New Donors/Year <span class='help-icon' title='Number of new donors acquired annually'>?</span>"),
376+
min = 0,
377+
max = 500,
378+
value = 100,
379+
step = 10)
380+
),
381+
382+
div(
383+
sliderInput("forecast_years",
384+
HTML("Forecast Years <span class='help-icon' title='Campaign timeline for projections (1-5 years)'>?</span>"),
385+
min = 1,
386+
max = 5,
387+
value = 3,
388+
step = 1)
389+
),
353390

354391
# Uncertainty Parameters
355392
div(class = "section-header", style = "font-size: 1.2rem; margin-top: 1rem;", "🎲 Variability"),
356393

357-
sliderInput("retention_variability",
358-
"Retention Variability",
359-
min = 0,
360-
max = 20,
361-
value = 5,
362-
step = 1,
363-
post = " pts"),
364-
365-
sliderInput("acquisition_variability",
366-
"Acquisition Variability",
367-
min = 0,
368-
max = 50,
369-
value = 15,
370-
step = 5,
371-
post = "%")
394+
div(
395+
sliderInput("retention_variability",
396+
HTML("Retention Variability <span class='help-icon' title='Year-to-year fluctuation in retention rate (±5 pts = 40-50% range)'>?</span>"),
397+
min = 0,
398+
max = 20,
399+
value = 5,
400+
step = 1,
401+
post = " pts")
402+
),
403+
404+
div(
405+
sliderInput("acquisition_variability",
406+
HTML("Acquisition Variability <span class='help-icon' title='Unpredictability in new donor acquisition (15% = 85-115 donors if planning for 100)'>?</span>"),
407+
min = 0,
408+
max = 50,
409+
value = 15,
410+
step = 5,
411+
post = "%")
412+
)
372413
),
373414

374415
# Main Panel with scenario tabs
@@ -676,28 +717,36 @@ server <- function(input, output, session) {
676717
data <- baseline_data()
677718
final_year <- data[nrow(data), ]
678719

679-
tagList(
680-
div(class = "metric-card",
681-
div(class = "metric-label", paste0("Most Likely Donors (Year ", input$forecast_years, ")")),
682-
div(class = "metric-value", comma(round(final_year$P50))),
683-
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
684-
paste0("Range: ", comma(round(final_year$P10)), " - ", comma(round(final_year$P90))))
720+
fluidRow(
721+
column(3,
722+
div(class = "metric-card",
723+
div(class = "metric-label", paste0("Most Likely Donors (Year ", input$forecast_years, ")")),
724+
div(class = "metric-value", comma(round(final_year$P50))),
725+
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
726+
paste0("Range: ", comma(round(final_year$P10)), " - ", comma(round(final_year$P90))))
727+
)
685728
),
686-
div(class = "metric-card",
687-
div(class = "metric-label", "Most Likely Revenue"),
688-
div(class = "metric-value", dollar(final_year$Revenue_P50)),
689-
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
690-
paste0("Range: ", dollar(round(final_year$Revenue_P10)), " - ", dollar(round(final_year$Revenue_P90))))
729+
column(3,
730+
div(class = "metric-card",
731+
div(class = "metric-label", "Most Likely Revenue"),
732+
div(class = "metric-value", dollar(final_year$Revenue_P50)),
733+
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
734+
paste0("Range: ", dollar(round(final_year$Revenue_P10)), " - ", dollar(round(final_year$Revenue_P90))))
735+
)
691736
),
692-
div(class = "metric-card",
693-
div(class = "metric-label", "Total Revenue (Median)"),
694-
div(class = "metric-value", dollar(sum(data$Revenue_P50)))
737+
column(3,
738+
div(class = "metric-card",
739+
div(class = "metric-label", "Total Revenue (Median)"),
740+
div(class = "metric-value", dollar(sum(data$Revenue_P50)))
741+
)
695742
),
696-
div(class = "metric-card",
697-
div(class = "metric-label", "Probability Analysis"),
698-
div(class = "metric-value", "80%"),
699-
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
700-
"Chance of staying within shown range")
743+
column(3,
744+
div(class = "metric-card",
745+
div(class = "metric-label", "Probability Analysis"),
746+
div(class = "metric-value", "80%"),
747+
p(style = "margin-top: 0.5rem; color: #666; font-size: 0.9rem;",
748+
"Chance of staying within shown range")
749+
)
701750
)
702751
)
703752
})

0 commit comments

Comments
 (0)