@@ -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