MEGA-BOQ-WEBAPP/static/theme.css

450 lines
16 KiB
CSS
Raw Permalink Normal View History

/* ==========================================================================
MEGA INFRA — BOQ Converter
Palette sampled from the MEGA INFRASTRUCTURE dashboard.
Warm neutrals are deliberate: #E1E1E1 sits on a warm axis and pairs with
#B3AC99. Pure #EEEEEE would read cold next to #FEA944.
All tokens live here so the brand can be retuned in one place.
========================================================================== */
:root {
/* surfaces */
--canvas: #1A202C; /* page background */
--topbar: #1A212D; /* header strip */
--surface: #262A37; /* cards + sidebar */
--surface-inset: #252A36; /* dashed "no data" */
--surface-raise: #363B49; /* hover, chips */
--hairline: #2F3442; /* 1px edge + dividers */
/* tinted status surfaces */
--success-fill: #22383E;
--success-edge: #1B534A;
--warn-fill: #3A373C;
--danger-fill: #3A2D38;
--chip: #443D3D;
/* accents */
--orange: #FEA944; /* brand, 7.47:1 */
--orange-soft: #F1A962; /* KPI numerals, 7.21:1 */
--orange-deep: #DF8441; /* bar gradient */
--green: #00CD8A; /* checks, links, 6.88:1 */
--red: #F04141; /* 3.77:1 - large only */
--red-soft: #FF6B6B; /* 5.15:1 - clears AA */
--red-deep: #8C363B;
/* text */
--text: #E1E1E1; /* 10.93:1 */
--text-muted: #999999; /* 5.02:1 */
--text-warm: #B3AC99; /* 6.32:1 */
--text-link: #00CD8A; /* 6.88:1 */
--serif: 'Fraunces', Georgia, serif;
--sans: 'Nunito', -apple-system, BlinkMacSystemFont, sans-serif;
--edge: 1px solid var(--hairline);
}
/* ---- base ---------------------------------------------------------------- */
html, body, .stApp, [data-testid="stAppViewContainer"] {
background: var(--canvas);
color: var(--text);
font-family: var(--sans);
}
h1, h2, h3, h4,
.stMarkdown h1, .stMarkdown h2, .stMarkdown h3, .stMarkdown h4 {
font-family: var(--serif) !important;
font-weight: 700 !important;
color: var(--text);
letter-spacing: -0.015em;
}
.stCaption, [data-testid="stCaptionContainer"] { color: var(--text-muted) !important; }
/* section heading: a hairline rule reads more corporate than bare text */
h2, .stMarkdown h2 {
position: relative;
padding-bottom: 14px !important;
margin-top: 6px !important;
}
h2::after, .stMarkdown h2::after {
content: "";
position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
background: linear-gradient(90deg, var(--orange-deep) 0%, var(--hairline) 26%, transparent 100%);
opacity: .85;
}
h3, .stMarkdown h3 { letter-spacing: .005em; }
a { color: var(--text-link) !important; }
code { background: var(--surface-raise) !important; color: var(--text-warm) !important; }
/* Streamlit's own top strip */
header[data-testid="stHeader"] { background: var(--topbar); }
[data-testid="stToolbar"] { background: transparent; }
[data-testid="stStatusWidget"] { color: var(--text-muted); }
/* ---- surfaces: cards ------------------------------------------------------ */
div[data-testid="stVerticalBlockBorderWrapper"],
div[data-testid="stVerticalBlock"] > div[data-testid="stElementContainer"] {
border-radius: 18px;
}
/* ---- KPI tiles -------------------------------------------------------------
Hand-built rather than st.metric: a row of four bare numbers is the generic
part of the page, and st.metric's label is plain text so it cannot carry an
icon. */
.boq-kpi {
position: relative;
background: linear-gradient(170deg, #2C3140 0%, var(--surface) 58%);
border: var(--edge);
border-radius: 18px;
padding: 16px 20px 18px;
overflow: hidden;
height: 100%;
transition: transform .3s cubic-bezier(.2,.7,.3,1),
border-color .3s ease, box-shadow .3s ease;
}
.boq-kpi::before {
content: "";
position: absolute; inset: 0 0 auto 0; height: 2px;
background: linear-gradient(90deg, transparent, rgba(254,169,68,.6), transparent);
opacity: .75;
}
.boq-kpi:hover {
transform: translateY(-3px);
border-color: rgba(254,169,68,.3);
box-shadow: 0 16px 36px -20px rgba(0,0,0,.9);
}
.boq-kpi-head {
display: flex; align-items: center; gap: 8px;
margin-bottom: 9px;
}
.boq-kpi-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 26px; height: 26px; border-radius: 8px; flex: none;
color: var(--orange);
background: rgba(254,169,68,.1);
border: 1px solid rgba(254,169,68,.18);
}
.boq-kpi-label {
font-family: var(--sans); font-size: .74rem; font-weight: 700;
letter-spacing: .07em; text-transform: uppercase; color: var(--text-muted);
}
.boq-kpi-value {
font-family: var(--serif); font-size: 2.15rem; font-weight: 700;
line-height: 1.05; letter-spacing: -.015em; color: var(--orange);
}
.boq-kpi-warn .boq-kpi-icon { color: var(--orange-soft); background: rgba(223,132,65,.12); border-color: rgba(223,132,65,.22); }
.boq-kpi-warn .boq-kpi-value { color: var(--orange-soft); }
.boq-kpi-ok .boq-kpi-icon { color: var(--green); background: rgba(0,205,138,.1); border-color: rgba(0,205,138,.2); }
.boq-kpi-ok .boq-kpi-value { color: var(--text); }
/* ---- field labels above the uploaders ------------------------------------- */
.boq-field-label {
display: flex; align-items: center; gap: 8px;
font-family: var(--sans); font-size: .8rem; font-weight: 700;
letter-spacing: .05em; text-transform: uppercase;
color: var(--text-muted); margin: 0 0 8px 2px;
}
.boq-field-label .boq-icon { color: var(--orange-deep); }
.boq-field-label b { color: var(--orange); font-weight: 800; }
.boq-field-label em { font-style: normal; text-transform: none;
letter-spacing: .02em; color: var(--text-muted); }
/* ---- section headings ------------------------------------------------------ */
.boq-section-title {
display: flex; align-items: center; gap: 12px;
font-family: var(--serif); font-size: 1.62rem; font-weight: 700;
letter-spacing: -.015em; color: var(--text);
margin: 30px 0 18px 0; padding-bottom: 15px;
border-bottom: 1px solid var(--hairline);
position: relative;
}
.boq-section-title::after {
content: "";
position: absolute; left: 0; bottom: -1px; height: 1px; width: 190px;
background: linear-gradient(90deg, var(--orange-deep), transparent);
}
.boq-section-title .boq-icon { color: var(--orange); }
.boq-section-sub {
display: flex; align-items: center; gap: 8px;
font-family: var(--sans); font-size: .8rem; font-weight: 700;
letter-spacing: .06em; text-transform: uppercase;
color: var(--text-muted); margin: 22px 0 10px 0;
}
.boq-section-sub .boq-icon { color: var(--orange-deep); }
/* ---- buttons -------------------------------------------------------------- */
.stButton > button,
.stDownloadButton > button {
font-family: var(--sans);
font-weight: 700;
font-size: 0.95rem;
border-radius: 999px;
min-height: 46px;
padding: 0 30px;
border: 1px solid transparent;
transition: transform 0.25s ease, filter 0.25s ease, background 0.25s ease;
}
.stButton > button[kind="primary"],
.stDownloadButton > button[kind="primary"] {
background: var(--orange);
color: var(--canvas);
border-color: var(--orange);
}
.stButton > button[kind="primary"]:hover,
.stDownloadButton > button[kind="primary"]:hover {
filter: brightness(1.08);
transform: translateY(-1px);
}
.stButton > button[kind="primary"]:active,
.stDownloadButton > button[kind="primary"]:active { transform: scale(0.98); }
.stButton > button[kind="secondary"] {
background: var(--surface);
color: var(--orange);
border: 1px solid var(--orange-deep);
}
.stButton > button[kind="secondary"]:hover { background: var(--surface-raise); }
.stDownloadButton > button[kind="secondary"] {
background: var(--surface);
color: var(--orange);
border: 1px solid var(--orange-deep);
}
.stDownloadButton > button[kind="secondary"]:hover {
background: var(--surface-raise);
transform: translateY(-1px);
}
/* ---- inputs --------------------------------------------------------------- */
[data-testid="stFileUploader"] section,
[data-testid="stTextInput"] input,
[data-testid="stSelectbox"] > div,
[data-testid="stNumberInput"] [data-baseweb="input"] {
border-radius: 12px !important;
background: var(--surface-inset) !important;
border: var(--edge) !important;
color: var(--text) !important;
transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
[data-testid="stTextInput"] input:focus,
[data-testid="stSelectbox"] > div:focus-within,
[data-testid="stNumberInput"] [data-baseweb="input"]:focus-within {
border-color: var(--orange) !important;
box-shadow: 0 0 0 3px rgba(254, 169, 68, 0.22) !important;
}
[data-testid="stFileUploader"] section:hover { border-color: var(--orange-deep) !important; }
[data-testid="stFileUploader"] small,
[data-testid="stWidgetLabel"] p,
label[data-testid="stWidgetLabel"] { color: var(--text-muted) !important; }
[data-testid="stFileUploader"] [data-testid="stFileUploaderFile"] { color: var(--text) !important; }
/* ---- sidebar -------------------------------------------------------------- */
section[data-testid="stSidebar"] {
background: var(--surface);
border-right: var(--edge);
}
section[data-testid="stSidebar"] .block-container { padding-top: 2.2rem; }
section[data-testid="stSidebar"] h3 { font-family: var(--serif); color: var(--text); }
section[data-testid="stSidebar"] .stCaption { color: var(--text-muted) !important; }
/* ---- tabs ----------------------------------------------------------------- */
[data-baseweb="tab-list"] { gap: 6px; background: transparent; }
[data-baseweb="tab"] {
font-family: var(--sans);
font-weight: 700;
font-size: 0.92rem;
color: var(--text-muted);
border-radius: 10px;
padding: 8px 18px;
transition: background 0.25s ease, color 0.25s ease;
}
[data-baseweb="tab"]:hover { background: var(--surface-raise); color: var(--text); }
[data-baseweb="tab"][aria-selected="true"] {
background: var(--surface-raise);
color: var(--orange);
}
[data-baseweb="tab-highlight"], [data-baseweb="tab-border"] { display: none; }
/* ---- alerts ---------------------------------------------------------------
Streamlit paints its own colour on an inner div, so that inner layer has
to be made transparent or it covers the brand surfaces underneath. */
[data-testid="stAlert"] {
border-radius: 16px;
border: var(--edge);
background: var(--surface) !important;
color: var(--text) !important;
}
[data-testid^="stAlertContent"] {
background: transparent !important;
color: var(--text) !important;
width: 100%;
}
[data-testid="stAlert"] p,
[data-testid="stAlert"] p *,
[data-testid="stAlert"] span,
[data-testid="stAlert"] strong { color: inherit !important; }
[data-testid="stAlert"] a { color: var(--text-link) !important; }
[data-testid="stAlertContentSuccess"] {
background: var(--success-fill) !important;
border-left: 3px solid var(--success-edge);
}
[data-testid="stAlertContentSuccess"] p { color: var(--green) !important; }
[data-testid="stAlertContentInfo"] { background: var(--surface-inset) !important; }
[data-testid="stAlertContentInfo"] p { color: var(--text) !important; }
[data-testid="stAlertContentWarning"] { background: var(--warn-fill) !important; }
[data-testid="stAlertContentWarning"] p { color: var(--orange-soft) !important; }
[data-testid="stAlertContentError"] { background: var(--danger-fill) !important; }
[data-testid="stAlertContentError"] p { color: var(--red-soft) !important; }
/* ---- brand lockup --------------------------------------------------------- */
.boq-brand {
display: flex; align-items: center; gap: 16px;
margin: 0 0 22px 0;
}
.boq-brand-mark {
display: flex; align-items: center; justify-content: center;
width: 56px; height: 56px; border-radius: 16px;
color: var(--canvas);
background: linear-gradient(145deg, var(--orange) 0%, var(--orange-deep) 100%);
box-shadow: 0 8px 22px -10px rgba(254, 169, 68, .75);
flex: none;
}
.boq-brand-mark .boq-icon { vertical-align: 0; }
.boq-brand-text { display: flex; flex-direction: column; gap: 3px; }
.boq-brand-name {
font-family: var(--serif); font-size: 2.15rem; font-weight: 800;
letter-spacing: -0.02em; line-height: 1.05; color: var(--text);
}
.boq-brand-sub {
font-family: var(--sans); font-size: 0.86rem; font-weight: 500;
color: var(--text-muted); line-height: 1.4;
}
/* ---- icons ---------------------------------------------------------------- */
.boq-icon {
vertical-align: -0.18em;
flex: none;
stroke-linecap: round;
stroke-linejoin: round;
}
/* ---- quiet inline note ----------------------------------------------------
Replaces the st.warning block. A filled alert bar shouts at the reader for
something that is background information; this sits at the same volume as
the legend it belongs next to. */
.boq-pill {
display: flex; align-items: flex-start; gap: 9px;
font-family: var(--sans); font-size: 0.845rem; line-height: 1.55;
color: var(--text-muted);
padding: 9px 14px; margin: 2px 0 14px 0;
border-radius: 12px;
background: rgba(38, 42, 55, .55);
border: 1px solid var(--hairline);
}
.boq-pill .boq-icon { margin-top: 2px; color: var(--text-muted); }
.boq-pill-ok { background: rgba(34, 56, 62, .5); border-color: rgba(27,83,74,.6); }
.boq-pill-ok .boq-icon { color: var(--green); }
.boq-pill-warn { background: rgba(58, 55, 60, .5); border-color: rgba(223,132,65,.4); }
.boq-pill-warn .boq-icon { color: var(--orange-soft); }
.boq-pill-info .boq-icon { color: var(--orange); }
/* ---- finding cards ---------------------------------------------------------
Findings are prose somebody has to read and act on, so they get cards
rather than a table cell that truncates. Long sentences wrap here. */
.boq-card {
background: var(--surface);
border: var(--edge);
border-left: 3px solid var(--orange-deep);
border-radius: 16px;
padding: 17px 22px;
margin-bottom: 10px;
transition: border-color 0.25s ease, background 0.25s ease;
}
.boq-card:hover { background: var(--surface-raise); }
.boq-card-meta {
font-family: var(--sans);
font-size: 0.7rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.09em;
font-weight: 700;
margin-bottom: 6px;
}
.boq-card-title {
font-family: var(--serif);
font-weight: 700;
font-size: 0.98rem;
color: var(--text);
margin-bottom: 5px;
overflow-wrap: anywhere;
}
.boq-card-body {
font-family: var(--sans);
font-size: 0.9rem;
line-height: 1.6;
color: var(--text-muted);
white-space: normal;
overflow-wrap: anywhere;
}
.boq-card-resolved { border-left-color: var(--green); }
/* severity - decided in Python (Finding.severity), not inferred from text.
A red edge must mean "a person must choose", full stop. */
.boq-card-decision { border-left-color: var(--red-deep); }
.boq-card-gap { border-left-color: var(--orange-deep); }
.boq-card-defect { border-left-color: var(--orange); }
.boq-card-note { border-left-color: var(--green); }
/* ---- resolution control --------------------------------------------------- */
.boq-resolve {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed var(--hairline);
}
.boq-legend {
display: flex; gap: 22px; flex-wrap: wrap;
font-size: 0.8rem; color: var(--text-muted); margin: 4px 0 16px 0;
}
.boq-legend b { font-weight: 700; }
.boq-swatch {
display: inline-block; width: 10px; height: 10px;
border-radius: 3px; margin-right: 7px; vertical-align: middle;
}
/* ---- dataframes ----------------------------------------------------------- */
[data-testid="stDataFrame"] {
border: var(--edge);
border-radius: 14px;
overflow: hidden;
background: var(--surface);
}
[data-testid="stDataFrame"] * { font-family: var(--sans); }
[data-testid="stDataFrame"] .glide-cell,
[data-testid="stDataFrame"] .glide-cell p,
[data-testid="stDataFrame"] div[role="gridcell"] {
white-space: normal !important;
line-height: 1.55;
}
[data-testid="stDataFrame"] div[role="columnheader"] { background: var(--surface-raise) !important; }
[data-testid="stDataFrame"] div[role="columnheader"] * { color: var(--text) !important; }
/* ---- expanders ------------------------------------------------------------ */
[data-testid="stExpander"] {
border: var(--edge);
border-radius: 12px;
background: var(--surface);
overflow: hidden;
}
[data-testid="stExpander"] summary { color: var(--text) !important; }
[data-testid="stExpander"] summary:hover { color: var(--orange) !important; }
/* ---- scrollbars ----------------------------------------------------------- */
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--canvas); }
::-webkit-scrollbar-thumb {
background: var(--surface-raise);
border-radius: 999px;
border: 3px solid var(--canvas);
}
::-webkit-scrollbar-thumb:hover { background: var(--orange-deep); }