MEGA-BOQ-WEBAPP/static/theme.css
MEGA INFRA 301d0fff44 MEGA INFRA BOQ Converter — Candy PDF to consultant Excel
Reads our Candy PDF (deterministically, no OCR or model needed) and populates
the consultant's own workbook using their formulas, column layout and PAYMENT
column. Nothing is ever silently overwritten: every disagreement is reported,
can be settled inside the app, and regenerated.

- extract   Candy text PDF -> tree (x-position column detection)
- template  layout detect, sequence align, write, totals, overrides
- validate  qty x rate and children-vs-subtotal reconciliation
- profiles  per-consultant layout profiles, stored on a volume

Shipped with a Dockerfile + docker-compose for Coolify.
2026-10-01 02:32:13 +02:00

449 lines
16 KiB
CSS

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