/* ========================================================================== 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); }