LowerLevelCards / theme.R
OwenStOnge's picture
Upload theme.R
fcc84d7 verified
Raw
History Blame Contribute Delete
11.1 kB
library(bslib)
library(glue)
HAWKS_ORANGE <- "#F25B00"
HAWKS_BLUE <- "#164EA7"
HAWKS_DARK <- "#0D1B2A"
HAWKS_LIGHT <- "#F5F7FA"
HAWKS_GRAY <- "#6B7280"
HAWKS_BORDER <- "#E2E8F0"
hawks_theme <- bs_theme(
version = 5,
bg = "#FFFFFF",
fg = HAWKS_DARK,
primary = HAWKS_BLUE,
secondary = HAWKS_ORANGE,
success = "#16a34a",
warning = "#d97706",
danger = "#dc2626",
base_font = font_google("Inter"),
heading_font = font_google("Inter"),
`border-radius` = "0.5rem",
`font-size-base` = "0.925rem"
)
# ── Shared CSS ────────────────────────────────────────────────────────────────
hawks_css <- tags$style(HTML(glue("
* {{ box-sizing: border-box; }}
body {{
background: {HAWKS_LIGHT};
font-family: 'Inter', system-ui, sans-serif;
}}
/* ── Sidebar shell ── */
.hawks-shell {{
display: flex;
height: 100vh;
overflow: hidden;
}}
.hawks-sidebar {{
width: 220px;
min-width: 220px;
background: {HAWKS_DARK};
display: flex;
flex-direction: column;
transition: width 0.2s ease, min-width 0.2s ease;
overflow: hidden;
}}
.hawks-sidebar.collapsed {{
width: 52px;
min-width: 52px;
}}
.sb-brand {{
padding: 16px 14px;
border-bottom: 1px solid #ffffff18;
display: flex;
align-items: center;
gap: 10px;
min-height: 56px;
}}
.sb-brand-text {{ overflow: hidden; white-space: nowrap; }}
.sb-brand-text .t1 {{ color: #fff; font-size: 13px; font-weight: 700; display: block; }}
.sb-brand-text .t2 {{ color: #ffffff55; font-size: 10px; display: block; letter-spacing: .03em; }}
.hawks-sidebar.collapsed .sb-brand-text {{ display: none; }}
.sb-section {{
padding: 12px 14px 4px;
color: #ffffff33;
font-size: 9px;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
white-space: nowrap;
}}
.hawks-sidebar.collapsed .sb-section {{ visibility: hidden; }}
.sb-item {{
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
margin: 1px 6px;
border-radius: 6px;
cursor: pointer;
color: #ffffffaa;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
text-decoration: none;
transition: background 0.15s, color 0.15s;
}}
.sb-item:hover {{ background: #ffffff12; color: #fff; }}
.sb-item.active {{ background: {HAWKS_BLUE}; color: #fff; }}
.sb-icon {{ font-size: 16px; width: 18px; text-align: center; flex-shrink: 0; }}
.sb-label {{ overflow: hidden; }}
.hawks-sidebar.collapsed .sb-label {{ display: none; }}
.sb-footer {{
margin-top: auto;
padding: 12px 6px;
border-top: 1px solid #ffffff18;
display: flex;
justify-content: center;
}}
.sb-toggle {{
width: 32px; height: 32px;
border-radius: 6px;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
color: #ffffff66;
transition: background 0.15s, color 0.15s;
}}
.sb-toggle:hover {{ background: #ffffff15; color: #fff; }}
/* ── Main area ── */
.hawks-main {{
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}}
.hawks-topbar {{
background: #fff;
border-bottom: 1px solid {HAWKS_BORDER};
padding: 0 20px;
height: 54px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}}
.topbar-title {{ font-size: 15px; font-weight: 700; color: {HAWKS_DARK}; }}
.topbar-sub {{ font-size: 11px; color: #9CA3AF; margin-left: 8px; }}
/* ── Content scroll area ── */
.hawks-content {{
flex: 1;
overflow-y: auto;
padding: 24px;
}}
/* ── Filter panel ── */
.hawks-body {{ display: flex; flex: 1; overflow: hidden; }}
.filter-panel {{
width: 210px;
min-width: 210px;
background: #fff;
border-right: 1px solid {HAWKS_BORDER};
padding: 16px 13px;
overflow-y: auto;
flex-shrink: 0;
}}
.fp-section {{
font-size: 10px; font-weight: 700; text-transform: uppercase;
letter-spacing: .08em; color: #9CA3AF; margin-bottom: 10px;
}}
.fp-group {{ margin-bottom: 16px; }}
.fp-label {{
font-size: 11px; font-weight: 600; color: #374151;
margin-bottom: 5px; display: block;
}}
.fp-input {{
width: 100%; background: {HAWKS_LIGHT};
border: 1px solid {HAWKS_BORDER}; border-radius: 5px;
padding: 5px 8px; font-size: 11px; color: #374151;
}}
.fp-pills {{ display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }}
.fp-pill {{
background: {HAWKS_LIGHT}; border: 1px solid {HAWKS_BORDER};
border-radius: 4px; padding: 3px 8px;
font-size: 10px; color: #374151; cursor: pointer;
}}
.fp-pill.active {{
background: #164EA714; border-color: #164EA755;
color: {HAWKS_BLUE}; font-weight: 600;
}}
.fp-apply {{
width: 100%; background: {HAWKS_BLUE}; color: #fff;
border: none; border-radius: 6px; padding: 7px;
font-size: 11px; font-weight: 600; cursor: pointer; margin-top: 4px;
}}
.fp-reset {{
text-align: center; font-size: 10px; color: #9CA3AF;
margin-top: 6px; cursor: pointer;
}}
/* ── Tab strip ── */
.hawks-tabs {{
background: #fff;
border-bottom: 1px solid {HAWKS_BORDER};
padding: 0 20px;
display: flex;
flex-shrink: 0;
}}
.h-tab {{
padding: 11px 14px;
font-size: 12px; font-weight: 500;
color: #6B7280;
border-bottom: 2px solid transparent;
cursor: pointer;
white-space: nowrap;
transition: color .15s, border-color .15s;
}}
.h-tab:hover {{ color: #374151; }}
.h-tab.active {{ color: {HAWKS_BLUE}; border-bottom-color: {HAWKS_BLUE}; font-weight: 600; }}
/* ── Stat cards ── */
.stat-grid {{
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 18px;
}}
.stat-box {{
background: #fff; border: 1px solid {HAWKS_BORDER};
border-radius: 8px; padding: 14px 16px;
}}
.stat-val {{ font-size: 22px; font-weight: 700; color: {HAWKS_DARK}; line-height: 1; }}
.stat-lbl {{
font-size: 10px; color: {HAWKS_GRAY};
text-transform: uppercase; letter-spacing: .07em; margin-top: 4px;
}}
.stat-delta {{ font-size: 10px; margin-top: 3px; }}
.stat-delta.up {{ color: #16a34a; }}
.stat-delta.dn {{ color: #dc2626; }}
.stat-bar {{ height: 3px; background: {HAWKS_BORDER}; border-radius: 2px; margin-top: 10px; }}
.stat-bar-fill {{ height: 3px; border-radius: 2px; background: {HAWKS_BLUE}; }}
/* ── Cards ── */
.data-card {{
background: #fff; border: 1px solid {HAWKS_BORDER};
border-radius: 8px; overflow: hidden;
}}
.data-card-hdr {{
padding: 10px 14px; border-bottom: 1px solid {HAWKS_BORDER};
display: flex; align-items: center; justify-content: space-between;
}}
.data-card-title {{
font-size: 11px; font-weight: 700; color: {HAWKS_DARK};
text-transform: uppercase; letter-spacing: .05em;
}}
.data-card-sub {{ font-size: 10px; color: #9CA3AF; }}
.data-card-body {{ padding: 12px 14px; }}
/* ── Player selector ── */
.player-select {{
background: {HAWKS_LIGHT}; border: 1px solid {HAWKS_BORDER};
border-radius: 6px; padding: 6px 10px;
display: flex; align-items: center; gap: 6px; cursor: pointer;
}}
.player-select .ps-name {{ font-size: 12px; font-weight: 600; color: #374151; }}
/* ── Placeholder ── */
.placeholder-panel {{
background: {HAWKS_LIGHT};
border: 1.5px dashed {HAWKS_BORDER};
border-radius: 8px;
padding: 3rem 1rem;
text-align: center; color: {HAWKS_GRAY};
}}
.placeholder-panel .ph-label {{ font-weight: 500; font-size: 0.9rem; }}
.placeholder-panel .ph-sub {{ font-size: 0.8rem; margin-top: 0.25rem; opacity: .7; }}
")))
# ── Sidebar builder ───────────────────────────────────────────────────────────
hawks_sidebar <- function(items, app_title = "Analytics") {
nav_items <- lapply(items, function(item) {
tags$div(
class = paste("sb-item", if (isTRUE(item$active)) "active" else ""),
id = paste0("sb_", item$id),
onclick = if (!is.null(item$tab)) {
sprintf("Shiny.setInputValue('active_tab','%s',{priority:'event'})", item$tab)
},
tags$i(class = paste("ti", item$icon, "sb-icon")),
tags$span(class = "sb-label", item$label)
)
})
tags$div(
class = "hawks-sidebar",
id = "hawksSidebar",
tags$div(
class = "sb-brand",
tags$svg(
width = "28", height = "28", viewBox = "0 0 30 30", fill = "none",
tags$polygon(points = "15,2 28,26 15,21 2,26", fill = HAWKS_ORANGE, opacity = "0.9"),
tags$polygon(points = "15,2 15,21 2,26", fill = "#FFFFFF", opacity = "0.18")
),
tags$div(
class = "sb-brand-text",
tags$span(class = "t1", "Harbor Hawks"),
tags$span(class = "t2", app_title)
)
),
tags$div(class = "sb-section", "Navigation"),
tagList(nav_items),
tags$div(
class = "sb-footer",
tags$div(
class = "sb-toggle",
id = "sbToggle",
onclick = "toggleSidebar()",
tags$i(class = "ti ti-chevrons-left", id = "sbToggleIcon")
)
)
)
}
# ── Topbar ────────────────────────────────────────────────────────────────────
hawks_topbar <- function(title, subtitle = "Cape Cod League \u00b7 2025", right = NULL) {
tags$div(
class = "hawks-topbar",
tags$div(
style = "display:flex; align-items:baseline; gap:6px;",
tags$span(class = "topbar-title", title),
tags$span(class = "topbar-sub", subtitle)
),
if (!is.null(right)) right
)
}
# ── Sidebar JS ────────────────────────────────────────────────────────────────
hawks_sidebar_js <- tags$script(HTML("
function toggleSidebar() {
var sb = document.getElementById('hawksSidebar');
var icon = document.getElementById('sbToggleIcon');
sb.classList.toggle('collapsed');
var isCollapsed = sb.classList.contains('collapsed');
icon.className = isCollapsed ? 'ti ti-chevrons-right' : 'ti ti-chevrons-left';
}
"))
# ── Helpers ───────────────────────────────────────────────────────────────────
placeholder_panel <- function(label, sub = "Coming soon") {
tags$div(
class = "placeholder-panel",
tags$div(class = "ph-label", label),
tags$div(class = "ph-sub", sub)
)
}