Files
vermietungs-tool/app/static/style.css
T
BlackMarkandClaude Opus 5.5 45424e6fd6 Vermietungs-Tool
A local web app for a small room-rental business in Austria: guest register,
room ledger, occupancy plan, invoices, the monthly tourist-tax report and the
quarterly VAT list. Application code only; it ships no data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:52:27 +01:00

289 lines
18 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root{
--ink:#1f2a24; --muted:#6b756e; --line:#d9e0da; --bg:#f4f6f3; --panel:#ffffff;
--accent:#2f6d5b; --accent-dark:#245645; --accent-soft:#e5efea;
--warn:#b4531a; --open:#b23a3a; --paid:#2e7d5b; --cash:#7a6a2e;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink);
font-family:"Segoe UI",system-ui,Arial,sans-serif;font-size:15px;line-height:1.45;
display:flex;flex-direction:column;height:100vh;overflow:hidden}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
.topbar{display:flex;flex-shrink:0;align-items:center;gap:28px;background:var(--accent);color:#fff;
padding:0 26px;height:56px;box-shadow:0 2px 6px rgba(0,0,0,.12)}
.topbar .brand{font-size:20px;font-weight:700;letter-spacing:.2px}
.topbar nav{display:flex;gap:6px;height:100%}
.topbar nav a{color:#e8f2ee;padding:0 18px;display:flex;align-items:center;height:100%;
font-size:17px;border-bottom:4px solid transparent}
.topbar nav a:hover{background:var(--accent-dark);text-decoration:none}
.topbar nav a.active{color:#fff;border-bottom-color:#fff;font-weight:600}
main{flex:1;min-height:0;overflow:auto;width:100%;max-width:1200px;margin:0 auto;padding:20px 26px 40px}
/* grid pages fill the viewport so only the table scrolls (no second page scrollbar) */
main.grid-page{max-width:none;overflow:hidden;display:flex;flex-direction:column;padding:12px 20px}
.grid-page h1{margin:2px 0 10px;font-size:22px}
.flash{background:#fff6e6;border:1px solid #e6c98a;color:#8a5a12;border-radius:8px;
padding:12px 16px;margin-bottom:18px;font-size:16px}
h1{font-size:28px;margin:4px 0 20px}
.toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap}
.toolbar .spacer{flex:1}
.count{color:var(--muted);font-size:15px}
.btn{display:inline-flex;align-items:center;gap:7px;background:var(--accent);color:#fff;
border:none;border-radius:8px;padding:10px 18px;font-size:16px;cursor:pointer;text-decoration:none}
.btn:hover{background:var(--accent-dark);text-decoration:none}
.btn.secondary{background:#eef2ef;color:var(--ink);border:1px solid var(--line)}
.btn.secondary:hover{background:#e3e9e4}
.btn.danger{background:#fff;color:var(--open);border:1px solid #e3b7b7}
.btn.danger:hover{background:#fbeeee}
.btn.small{padding:6px 12px;font-size:14px}
.btn:disabled{opacity:.4;cursor:not-allowed}
input[type=text],input[type=date],input[type=number],select{
font-size:16px;padding:9px 11px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink)}
input:focus,select:focus{outline:2px solid var(--accent-soft);border-color:var(--accent)}
.search{min-width:280px}
/* overflow-anchor off: the grids swap windowed rows in and out while scrolling — the browser's scroll
anchoring would fight that and make the list jump */
.tablewrap{background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:auto;
overflow-anchor:none}
.grid-page .tablewrap{flex:1;min-height:0}
table{border-collapse:collapse;width:100%;font-size:13px}
thead th{position:sticky;top:0;z-index:1;background:var(--accent-soft);color:var(--accent-dark);text-align:left;
padding:6px 9px;font-weight:600;white-space:nowrap;border-bottom:1px solid var(--line);
cursor:pointer;user-select:none}
/* headers carry full names; th-abbr (set by grids.js when the table overflows its container)
abbreviates them with an ellipsis — the tooltip keeps the full name */
table.th-abbr thead th{max-width:78px;overflow:hidden;text-overflow:ellipsis}
/* pin toggle (Spalte fixieren): revealed on header hover at the LEFT edge — the sort arrows own the
right edge. Grid headers reserve room for it so the glyph never covers the title. */
table.grid thead th{padding-left:20px}
thead th .pinbtn{position:absolute;left:2px;top:50%;transform:translateY(-50%);
font-size:10px;line-height:1;padding:2px;cursor:pointer;opacity:0}
thead th:hover .pinbtn{opacity:.45}
thead th .pinbtn:hover,thead th.pinned .pinbtn{opacity:1}
thead th:hover{background:#d7e6df}
/* sort indicator, pinned to the header's right edge so an abbreviated title can't swallow it;
with several sort levels (shift-click) the priority number follows the arrow */
thead th[data-sd]{padding-right:24px}
thead th[data-sd]::after{position:absolute;right:6px;font-size:10px}
thead th[data-sd="a"]::after{content:"\25B2" attr(data-prio)}
thead th[data-sd="d"]::after{content:"\25BC" attr(data-prio)}
tbody td{padding:2px 9px;border-bottom:1px solid #eef1ee;white-space:nowrap}
/* the ledger has 20 columns — give its cells less air so more of them fit on screen */
table.grid tbody td{padding-left:7px;padding-right:7px}
table.grid thead th{padding-left:14px;padding-right:7px}
table.grid td.actions .btn.small{padding:4px 9px}
/* a right-aligned number needs no room on its LEFT, and the pin glyph can't collide with it either —
so Satz/Anz/Pers/Betrag and the checkbox column give that space back */
table.grid thead th.num,table.grid tbody td.num{padding-left:4px}
table.grid thead th:first-child,table.grid tbody td:first-child{padding-left:5px;padding-right:5px}
tbody tr:nth-child(even){background:#fafcfb}
tbody tr:hover{background:var(--accent-soft)}
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
td.actions{text-align:right}
.empty{padding:40px;text-align:center;color:var(--muted)}
.pill{display:inline-block;padding:2px 10px;border-radius:999px;font-size:13px;font-weight:600}
.pill.paid{background:#e4f0ea;color:var(--paid)}
.pill.open{background:#f7e3e3;color:var(--open)}
.pill.cash{background:#f2eed9;color:var(--cash)}
.pill.unit{background:#e9edf5;color:#3a4a6b}
.pill.have{background:#eef1ef;color:#5a6b62}
/* the whole text of a cut-off list cell, on hover (grids.js showTip) */
.celltip{position:fixed;z-index:50;display:none;max-width:480px;padding:6px 11px;background:var(--panel);
color:var(--ink);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.15);
font-size:15px;line-height:1.35;white-space:normal;pointer-events:none}
/* Umsatzsteuer-Voranmeldung: one table with its sum per room, the summary at the end */
.vat-room h2,.vat-total h2{font-size:19px;margin:22px 0 6px}
.vat-room tfoot td{padding:4px 9px;font-weight:700;border-top:2px solid var(--line)}
.vat-total table{width:auto;min-width:420px;font-size:16px;background:var(--panel);
border:1px solid var(--line);border-radius:10px}
.vat-total td{padding:5px 14px;border-bottom:1px solid #eef1ee}
.vat-total td.num{padding-left:40px}
.vat-total tr.key td{font-weight:700}
/* home */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin:20px 0 30px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 22px}
.card .n{font-size:34px;font-weight:700;color:var(--accent-dark)}
.card .l{color:var(--muted);margin-top:4px}
.lead{font-size:18px;color:var(--muted);max-width:60ch}
.quicklinks{display:flex;gap:14px;flex-wrap:wrap;margin-top:10px}
/* forms */
form.card{max-width:720px;margin:0 auto}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
.field label{font-weight:600;font-size:15px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
/* an invoice dated against the number order: the deliberate exception (invoice_form) */
.confirm-order{display:flex;align-items:center;gap:8px;margin:2px 0 14px;padding:8px 12px;font-size:15px;
background:#fff6e6;border:1px solid #e6c98a;border-radius:8px;color:#8a5a12}
.client-choices{flex-direction:column;align-items:flex-start}
.client-choices .btn.picked{background:var(--accent);color:#fff}
/* payments in parts: one line of date + amount each (ledger_form) */
.pay-line{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:8px}
#addPayment{align-self:flex-start;margin-bottom:6px}
.formactions{display:flex;gap:12px;margin-top:24px}
/* Belegungsplan: rooms × days timeline. The day columns are fractional (1fr), so the plan always
fills the viewport width exactly — no horizontal scrolling at any display scaling (up to 200%).
Only the room rows scroll vertically; the date header sticks. */
.planwrap{overflow-y:auto;overflow-x:hidden}
.plan{--labelw:112px;--laneh:26px;min-width:600px}
.plan-hrow,.plan-row{display:flex}
.plan-row{border-bottom:1px solid var(--line)}
.plan-row.newfloor{border-top:2px solid var(--line)}
.plan-label{flex:0 0 var(--labelw);padding:4px 10px;border-right:1px solid var(--line);min-width:0}
.plan-cells{flex:1;display:grid;grid-template-columns:repeat(var(--days),1fr);min-width:0}
.plan-head{position:sticky;top:0;z-index:3;background:var(--panel);box-shadow:0 2px 4px rgba(0,0,0,.07)}
/* the month row closes with a full-width line */
.plan-head .plan-hrow:first-child{border-bottom:1px solid var(--line)}
.plan-head .plan-hrow + .plan-hrow .plan-label{border-bottom:1px solid var(--line)}
.pm{font-size:12px;font-weight:600;color:var(--accent-dark);padding:3px 5px 1px 8px;
border-left:1px solid #b9c4bb;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* the label column already draws the divider — no doubled line on the first day column */
.pm:first-child,.pd:first-child{border-left:none}
.pd{border-left:1px solid #eef1ee;border-bottom:1px solid var(--line);text-align:center;
padding:1px 0 3px;overflow:visible;min-width:0}
.pd b{display:block;font-size:12px;line-height:1.25;white-space:nowrap}
.pd span{display:block;font-size:10px;color:var(--muted);line-height:1.2}
.pd.we{background:#f2f4f0}
.pd.mon{border-left-color:var(--line)}
/* weekend and today columns are bounded on BOTH sides ('x + y' colors the following cell's left
border = this column's end line), at normal 1px line thickness. Precedence weekend < month <
today is enforced by DOUBLED classes (today is often also .we — a Sunday — and the '+' selectors
carry an extra element of specificity that a single class cannot beat). */
.pd.we,.pd.we + .pd{border-left-color:#c9d2ca}
.pd.m1.m1{border-left-color:#b9c4bb} /* month boundary: continuous from the month row down */
.pd.today{background:#fff3d6}
.pd.today.today,.pd.today.today + .pd{border-left-color:var(--warn)}
.pd.today b{color:var(--warn)}
.plan-lanes{grid-template-rows:repeat(var(--lanes),var(--laneh));position:relative;
align-content:center}
/* room rows carry only neutral day grid lines — the weekend/today accents live in the header
alone. The lines are a full-height absolute overlay so they span the whole row even when the
room label makes the row taller than the lane stack. */
.daylines{position:absolute;inset:0;display:grid;grid-template-columns:repeat(var(--days),1fr);
pointer-events:none}
.daylines i{border-left:1px solid #f0f3ef}
.daylines i.mon{border-left-color:var(--line)}
.daylines i.we,.daylines i.we + i{border-left-color:#c9d2ca}
.daylines i.m1.m1{border-left-color:#b9c4bb}
.daylines i.today.today,.daylines i.today.today + i{border-left-color:var(--warn)}
.daylines i:first-child{border-left:none}
/* weekend/today column tints, same hues as the header */
.daylines i.we{background:#f2f4f0}
.daylines i.today{background:#fff3d6}
.plan-lanes.longterm{background:repeating-linear-gradient(45deg,#f3f5f2 0 8px,#eaeeea 8px 16px)}
/* ALL grid lines (month boundaries included) sit BEHIND cell content — the opaque
Dauervermietung filling covers its whole row, so these rows draw no lines or tints */
.plan-lanes.longterm .daylines{display:none}
.lt-note{grid-column:1/-1;grid-row:1/-1;align-self:center;padding:0 10px;color:var(--muted);
font-size:12px;z-index:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rl-top{display:flex;align-items:center;gap:6px;white-space:nowrap}
.rl-top b{font-size:14px}
.rl-top .pill{font-size:11px;padding:1px 7px}
.rl-sub{font-size:11px;color:var(--muted);white-space:nowrap}
.bar{z-index:1;margin:2px 1px;border-radius:5px;font-size:12.5px;line-height:calc(var(--laneh) - 5px);
padding:0 7px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;color:#fff}
.bar:hover{text-decoration:none;filter:brightness(1.08)}
.bar.current{background:var(--accent)}
.bar.past{background:#b9c9c0;color:#2c3a32}
.bar.future{background:var(--accent-soft);color:var(--accent-dark);box-shadow:inset 0 0 0 1px var(--accent)}
/* a stay running past the window edge (or an open departure) gets a pointed end */
.bar.clipend{clip-path:polygon(0 0,calc(100% - 8px) 0,100% 50%,calc(100% - 8px) 100%,0 100%);padding-right:13px}
.bar.clipstart{clip-path:polygon(8px 0,100% 0,100% 100%,8px 100%,0 50%);padding-left:13px}
.bar.clipstart.clipend{clip-path:polygon(8px 0,calc(100% - 8px) 0,100% 50%,calc(100% - 8px) 100%,8px 100%,0 50%)}
.plan-foot{flex-shrink:0;display:flex;gap:16px;align-items:center;flex-wrap:wrap;
padding:8px 2px 0;color:var(--muted);font-size:13px}
.plan-foot .key{display:inline-block;width:14px;height:14px;border-radius:4px;
vertical-align:-2px;margin-right:5px}
.plan-unplaced a{margin-left:2px}
.seg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff}
.seg a{padding:7px 13px;font-size:14px;color:var(--ink)}
.seg a:hover{background:#eef2ef;text-decoration:none}
.seg a.on{background:var(--accent);color:#fff}
/* at 150–200% display scaling (≈960–1280 CSS px on a 24" monitor) the chrome steps down so no page
ever grows a horizontal scrollbar */
@media (max-width:1280px){
.topbar{gap:16px;padding:0 14px}
.topbar .brand{font-size:17px}
.topbar nav a{padding:0 11px;font-size:15px}
}
@media (max-width:1020px){
.topbar .brand{display:none}
main{padding-left:14px;padding-right:14px}
main.grid-page{padding:10px 12px}
}
/* printing an app page (e.g. the TVB Meldung for the folder): the app shell scrolls inside a fixed
viewport, which would clip everything to one page — undo that and drop chrome/warnings.
MUST stay at the END of the file: several rules here tie on specificity with base rules above
(tbody td, body) and win only by source order. */
.printinfo{display:none}
/* Print view. Two roads lead here: Ctrl+P (media print) and the Drucken button, which measures the page
BEFORE printing to shrink it onto one sheet — for that it must already look on screen as it will on
paper, or the toolbar is measured too. Hence the same rules under body.printing (measuring) and
@media print (paper). */
body.printing .topbar,body.printing .toolbar,body.printing .flash{display:none}
body.printing{background:#fff;height:auto;overflow:visible;display:block}
body.printing main{overflow:visible;height:auto;max-width:none;padding:0;background:#fff}
body.printing .tablewrap{max-height:none;overflow:visible;box-shadow:none;border:0;background:#fff}
body.printing .printinfo{display:block;margin:0 0 8px;font-size:12px;color:#333}
body.printing .grid th,body.printing .grid td{position:static}
body.printing tbody td{white-space:normal}
/* Umsatzsteuer list: only the name may wrap — otherwise every cell breaks on paper ("2026-/49") */
.vat-room td,body.printing .vat-room td{white-space:nowrap}
.vat-room tbody td:nth-child(2),body.printing .vat-room tbody td:nth-child(2){white-space:normal}
/* On screen the columns are frozen to fixed pixel widths (freezeLayout, flex rows). On paper the table
must be a real table again: then the columns shrink to their content and long texts wrap instead of
pushing the list past the sheet's edge. */
body.printing .grid,body.printing .grid thead,body.printing .grid tbody,body.printing .grid tr,
body.printing .grid th,body.printing .grid td{display:revert!important;min-width:0!important}
body.printing .grid{width:100%!important;table-layout:auto!important;border-collapse:collapse}
body.printing .grid thead{display:table-header-group!important;position:static!important}
body.printing .grid th,body.printing .grid td{width:auto!important;max-width:none!important;
overflow:visible!important;text-overflow:clip!important;white-space:normal!important;
padding:2px 5px!important;font-size:12px!important;line-height:1.25!important}
body.printing .grid tr{break-inside:avoid}
body.printing .grid td.num,body.printing .grid .nowrap{white-space:nowrap!important}
/* after the display:revert rules, or revert wins over the hiding */
body.printing .grid .selcol,body.printing .grid .actions,body.printing .pinbtn{display:none!important}
body.printing .grid .pill{border:0;padding:0;background:none!important;color:inherit!important;font-weight:600}
@media print {
/* Landscape for the wide lists (the named page "list"); everything else portrait — the TVB report sets
its landscape itself (tvb.html). Printed duplex in landscape, the printer must flip "on the short
edge", or the back side stands on its head — no page can set that itself. */
@page{size:A4 portrait;margin:10mm}
@page list{size:A4 landscape;margin:10mm}
main.grid-page{page:list}
body{display:block;height:auto;overflow:visible;background:#fff}
.topbar,.noprint,.flash,.toolbar,.celltip{display:none!important}
main{overflow:visible;height:auto;max-width:none;padding:0;background:#fff}
.tablewrap{max-height:none!important;overflow:visible;box-shadow:none;border:0;background:#fff}
tbody td{white-space:normal} /* long Hinweis texts wrap instead of running off the sheet */
td.note div{max-width:240px} /* cells ignore max-width in auto layout — cap the inner block */
.tvb-unit + .tvb-unit{page-break-before:always}
.tvb-summary,.tvb-summary .tablewrap{break-inside:avoid} /* Feratel tables never split over pages */
.tvb-feratel{break-inside:avoid} /* ... and their heading and total travel WITH them */
.vat-room,.vat-total{break-inside:avoid} /* a room with its sum on one sheet */
/* the list on paper: checkboxes, the edit button and the pin say nothing there, pinned columns must
not stick over the others, the header row repeats on every sheet */
.grid .selcol,.grid .actions,.pinbtn{display:none}
.grid th,.grid td{position:static!important}
.grid thead{display:table-header-group}
.grid tr{break-inside:avoid}
.printinfo{display:block;margin:0 0 8px;font-size:12px;color:#333}
}