: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} }