frontend: hoist inline <style> into /static/app.css, drop redundant hx-on
base.html shrinks from a 150-line inline stylesheet to a single <link>; the CSS moves to web/static/app.css byte-for-byte so there's no visual change, but the stylesheet is now cacheable independently of the HTML. Drop hx-on::before-request="this.disabled=true" from the Bewerben / Ablehnen buttons — it duplicates hx-disabled-elt="find button" on the parent form, which htmx already applies per request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb617dd38a
commit
4f23726e8f
3 changed files with 166 additions and 158 deletions
163
web/static/app.css
Normal file
163
web/static/app.css
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/* wohnungsdidi — site styling.
|
||||
Imported via <link> from base.html. Tailwind utilities (via CDN) handle
|
||||
layout/spacing; this file owns our design tokens and component classes. */
|
||||
|
||||
:root {
|
||||
--bg-from: #e4f0fb; --bg-to: #f7fbfe;
|
||||
--surface: #ffffff; --border: #d8e6f3;
|
||||
--text: #10253f; --muted: #667d98;
|
||||
--primary: #2f8ae0; --primary-hover: #1f74c8;
|
||||
--danger: #e05a6a; --danger-hover: #c44a59;
|
||||
--ghost: #eaf2fb; --ghost-hover: #d5e5f4;
|
||||
--accent: #fbd76b;
|
||||
}
|
||||
|
||||
html { color-scheme: light; }
|
||||
body {
|
||||
background: linear-gradient(180deg, var(--bg-from) 0%, var(--bg-to) 100%);
|
||||
background-attachment: fixed;
|
||||
color: var(--text);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, sans-serif;
|
||||
}
|
||||
|
||||
/* Card + separator hooks */
|
||||
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
||||
box-shadow: 0 1px 2px rgba(16, 37, 63, 0.04); }
|
||||
.border-soft { border-color: var(--border) !important; }
|
||||
.divide-soft > :not([hidden]) ~ :not([hidden]) { border-color: var(--border) !important; }
|
||||
|
||||
/* Buttons */
|
||||
.btn { border-radius: 9px; padding: 0.45rem 0.95rem; font-weight: 500;
|
||||
transition: background 0.15s, box-shadow 0.15s, transform 0.05s; display: inline-block; }
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn:disabled, .btn[disabled] { opacity: .55; cursor: not-allowed; pointer-events: none; }
|
||||
.btn-primary { background: var(--primary); color: white; box-shadow: 0 1px 2px rgba(47,138,224,.25); }
|
||||
.btn-primary:hover { background: var(--primary-hover); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-danger:hover { background: var(--danger-hover); }
|
||||
.btn-ghost { background: var(--ghost); color: var(--text); border: 1px solid var(--border); }
|
||||
.btn-ghost:hover { background: var(--ghost-hover); }
|
||||
|
||||
/* Auto-apply hot button (unused in current UI, kept for future) */
|
||||
.btn-hot { background: linear-gradient(135deg, #ff7a85 0%, #e14a56 100%); color: white;
|
||||
box-shadow: 0 2px 6px rgba(225, 74, 86, 0.35); font-weight: 600; }
|
||||
.btn-hot:hover { filter: brightness(1.05); }
|
||||
.btn-hot.off { background: linear-gradient(135deg, #cfd9e6 0%, #99abc2 100%);
|
||||
box-shadow: 0 1px 2px rgba(16, 37, 63, 0.15); }
|
||||
|
||||
/* Partner-Aktionsbadge — kleiner Kreis oben rechts am Button */
|
||||
.btn-with-badge { position: relative; display: inline-block; }
|
||||
.partner-badge {
|
||||
position: absolute; top: -6px; right: -6px;
|
||||
width: 18px; height: 18px; border-radius: 9999px;
|
||||
background: var(--primary); color: #fff;
|
||||
font-size: 10px; font-weight: 700; line-height: 1;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 2px rgba(16,37,63,.25);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.input { background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 0.55rem 0.8rem; width: 100%; color: var(--text);
|
||||
transition: border-color .15s, box-shadow .15s; }
|
||||
.input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(47,138,224,.18); }
|
||||
|
||||
/* Chips */
|
||||
.chip { padding: .2rem .7rem; border-radius: 999px; font-size: .75rem; font-weight: 500; display: inline-block; }
|
||||
.chip-ok { background: #e4f6ec; color: #1f8a4a; border: 1px solid #b7e4c7; }
|
||||
.chip-warn { background: #fff4dd; color: #a36a1f; border: 1px solid #f5d48b; }
|
||||
.chip-bad { background: #fde6e9; color: #b8404e; border: 1px solid #f5b5bf; }
|
||||
.chip-info { background: #e3effc; color: #1f5f99; border: 1px solid #b6d4f0; }
|
||||
|
||||
/* Countdown — tabular digits + fixed width so the text doesn't wobble
|
||||
while the seconds tick down. */
|
||||
.countdown { font-variant-numeric: tabular-nums; display: inline-block;
|
||||
min-width: 4.2em; text-align: left; }
|
||||
.sep { color: var(--muted); user-select: none; }
|
||||
|
||||
/* iOS-style toggle switch */
|
||||
.switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
|
||||
.switch-visual { position: absolute; cursor: pointer; inset: 0;
|
||||
background: #cfd9e6; border-radius: 999px; transition: background .2s; }
|
||||
.switch-visual::before { content: ""; position: absolute; width: 20px; height: 20px;
|
||||
left: 3px; top: 3px; background: #fff; border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(16,37,63,0.25); transition: transform .2s; }
|
||||
.switch input:checked + .switch-visual { background: var(--primary); }
|
||||
.switch input:checked + .switch-visual::before { transform: translateX(20px); }
|
||||
.switch.warn input:checked + .switch-visual { background: var(--danger); }
|
||||
.switch input:focus-visible + .switch-visual { box-shadow: 0 0 0 3px rgba(47,138,224,.25); }
|
||||
|
||||
/* View toggle (Liste / Karte) — segmented pill, CSS-only via :has() */
|
||||
.view-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 999px;
|
||||
overflow: hidden; background: var(--surface); font-size: 0.85rem; font-weight: 500; }
|
||||
.view-toggle label { padding: 0.35rem 0.95rem; cursor: pointer; user-select: none;
|
||||
color: var(--muted); transition: background .15s, color .15s; }
|
||||
.view-toggle input { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }
|
||||
.view-toggle label:hover { color: var(--text); background: var(--ghost); }
|
||||
.view-toggle label:has(input:checked) { background: var(--primary); color: #fff; }
|
||||
.view-map { display: none; }
|
||||
body:has(#v_map:checked) .view-list { display: none; }
|
||||
body:has(#v_map:checked) .view-map { display: block; }
|
||||
#flats-map { height: 520px; border-radius: 10px; }
|
||||
|
||||
/* Flat detail expand */
|
||||
.flat-row { border-top: 1px solid var(--border); }
|
||||
.flat-row:first-child { border-top: 0; }
|
||||
.flat-expand-btn { width: 1.75rem; height: 1.75rem; border-radius: 999px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 1px solid var(--border); background: var(--surface);
|
||||
color: var(--muted); cursor: pointer; transition: transform .2s, background .15s; }
|
||||
.flat-expand-btn:hover { background: var(--ghost); color: var(--text); }
|
||||
.flat-expand-btn.open { transform: rotate(180deg); }
|
||||
.flat-detail { background: #fafcfe; border-top: 1px solid var(--border); }
|
||||
.flat-detail:empty { display: none; }
|
||||
|
||||
/* Normalised image gallery — every tile has the same aspect ratio */
|
||||
.flat-gallery { display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 8px; }
|
||||
.flat-gallery-tile { aspect-ratio: 4 / 3; overflow: hidden;
|
||||
border-radius: 8px; border: 1px solid var(--border);
|
||||
background: #f0f5fa; display: block; }
|
||||
.flat-gallery-tile img { width: 100%; height: 100%; object-fit: cover;
|
||||
display: block; transition: transform .3s; }
|
||||
.flat-gallery-tile:hover img { transform: scale(1.04); }
|
||||
|
||||
/* Leaflet popup — match site visual */
|
||||
.leaflet-popup-content-wrapper { border-radius: 12px; box-shadow: 0 6px 20px rgba(16,37,63,.15); }
|
||||
.leaflet-popup-content { margin: 12px 14px; min-width: 220px; color: var(--text); }
|
||||
.map-popup-title { font-weight: 600; font-size: 13px; display: inline-block; color: var(--primary); }
|
||||
.map-popup-title:hover { text-decoration: underline; }
|
||||
.map-popup-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
.map-popup-status { margin-top: 8px; }
|
||||
.map-popup-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
|
||||
.map-popup-actions .btn { padding: 0.35rem 0.7rem; font-size: 12px; }
|
||||
.map-popup-actions form { margin: 0; }
|
||||
|
||||
/* Brand avatar */
|
||||
.brand-dot {
|
||||
width: 2.5rem; height: 2.5rem; border-radius: 10px;
|
||||
background: linear-gradient(135deg, #66b7f2 0%, #2f8ae0 60%, #fbd76b 100%);
|
||||
box-shadow: 0 1px 4px rgba(47, 138, 224, 0.35);
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
overflow: hidden; flex-shrink: 0;
|
||||
}
|
||||
.brand-dot img { width: 88%; height: 88%; object-fit: contain; display: block; }
|
||||
|
||||
/* Anchors */
|
||||
a { color: var(--primary); }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* Tab nav */
|
||||
.tab { padding: 0.7rem 0.2rem; color: var(--muted); border-bottom: 2px solid transparent;
|
||||
margin-right: 1.5rem; font-weight: 500; }
|
||||
.tab.active { color: var(--text); border-color: var(--primary); }
|
||||
.tab:hover { color: var(--text); text-decoration: none; }
|
||||
|
||||
/* Mono / forensic JSON tree */
|
||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 12px; }
|
||||
details > summary { cursor: pointer; user-select: none; }
|
||||
details > summary::marker { color: var(--muted); }
|
||||
|
|
@ -158,8 +158,7 @@
|
|||
<input type="hidden" name="flat_id" value="{{ f.id }}">
|
||||
<button class="btn btn-primary text-sm" type="submit"
|
||||
{% if is_running %}disabled{% endif %}
|
||||
hx-confirm="Bewerbung für {{ (f.address or f.link)|e }} starten?"
|
||||
hx-on::before-request="this.disabled=true">
|
||||
hx-confirm="Bewerbung für {{ (f.address or f.link)|e }} starten?">
|
||||
Bewerben
|
||||
</button>
|
||||
{% if partner and f.id in partner.applied_flat_ids %}
|
||||
|
|
@ -176,8 +175,7 @@
|
|||
<input type="hidden" name="flat_id" value="{{ f.id }}">
|
||||
<button class="btn btn-ghost text-sm" type="submit"
|
||||
{% if is_running %}disabled{% endif %}
|
||||
hx-confirm="Ablehnen und aus der Liste entfernen?"
|
||||
hx-on::before-request="this.disabled=true">
|
||||
hx-confirm="Ablehnen und aus der Liste entfernen?">
|
||||
Ablehnen
|
||||
</button>
|
||||
{% if partner and f.id in partner.rejected_flat_ids %}
|
||||
|
|
|
|||
|
|
@ -12,162 +12,9 @@
|
|||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
<script src="/static/app.js" defer></script>
|
||||
<script src="/static/map.js" defer></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg-from: #e4f0fb; --bg-to: #f7fbfe;
|
||||
--surface: #ffffff; --border: #d8e6f3;
|
||||
--text: #10253f; --muted: #667d98;
|
||||
--primary: #2f8ae0; --primary-hover: #1f74c8;
|
||||
--danger: #e05a6a; --danger-hover: #c44a59;
|
||||
--ghost: #eaf2fb; --ghost-hover: #d5e5f4;
|
||||
--accent: #fbd76b;
|
||||
}
|
||||
html { color-scheme: light; }
|
||||
body {
|
||||
background: linear-gradient(180deg, var(--bg-from) 0%, var(--bg-to) 100%);
|
||||
background-attachment: fixed;
|
||||
color: var(--text);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Inter, sans-serif;
|
||||
}
|
||||
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
|
||||
box-shadow: 0 1px 2px rgba(16, 37, 63, 0.04); }
|
||||
.border-soft { border-color: var(--border) !important; }
|
||||
.divide-soft > :not([hidden]) ~ :not([hidden]) { border-color: var(--border) !important; }
|
||||
.btn { border-radius: 9px; padding: 0.45rem 0.95rem; font-weight: 500;
|
||||
transition: background 0.15s, box-shadow 0.15s, transform 0.05s; display: inline-block; }
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn:disabled, .btn[disabled] { opacity: .55; cursor: not-allowed; pointer-events: none; }
|
||||
|
||||
/* Partner-Aktionsbadge — kleiner Kreis mit dem Anfangsbuchstaben oben rechts am Button */
|
||||
.btn-with-badge { position: relative; display: inline-block; }
|
||||
.partner-badge {
|
||||
position: absolute; top: -6px; right: -6px;
|
||||
width: 18px; height: 18px; border-radius: 9999px;
|
||||
background: var(--primary); color: #fff;
|
||||
font-size: 10px; font-weight: 700; line-height: 1;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 2px rgba(16,37,63,.25);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.btn-primary { background: var(--primary); color: white; box-shadow: 0 1px 2px rgba(47,138,224,.25); }
|
||||
.btn-primary:hover { background: var(--primary-hover); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-danger:hover { background: var(--danger-hover); }
|
||||
.btn-ghost { background: var(--ghost); color: var(--text); border: 1px solid var(--border); }
|
||||
.btn-ghost:hover { background: var(--ghost-hover); }
|
||||
.input { background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
|
||||
padding: 0.55rem 0.8rem; width: 100%; color: var(--text);
|
||||
transition: border-color .15s, box-shadow .15s; }
|
||||
.input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(47,138,224,.18); }
|
||||
.chip { padding: .2rem .7rem; border-radius: 999px; font-size: .75rem; font-weight: 500; display: inline-block; }
|
||||
/* Countdown — tabular digits + fixed width so the text doesn't wobble
|
||||
while the seconds tick down (3 → 10 → 59 → 1 min). */
|
||||
.countdown { font-variant-numeric: tabular-nums; display: inline-block;
|
||||
min-width: 4.2em; text-align: left; }
|
||||
/* Neutral separator in flex rows so the '·' has equal gap on both sides. */
|
||||
.sep { color: var(--muted); user-select: none; }
|
||||
.chip-ok { background: #e4f6ec; color: #1f8a4a; border: 1px solid #b7e4c7; }
|
||||
.chip-warn { background: #fff4dd; color: #a36a1f; border: 1px solid #f5d48b; }
|
||||
.chip-bad { background: #fde6e9; color: #b8404e; border: 1px solid #f5b5bf; }
|
||||
.chip-info { background: #e3effc; color: #1f5f99; border: 1px solid #b6d4f0; }
|
||||
/* iOS-style toggle switch */
|
||||
.switch { position: relative; display: inline-block; width: 46px; height: 26px;
|
||||
flex-shrink: 0; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
|
||||
.switch-visual { position: absolute; cursor: pointer; inset: 0;
|
||||
background: #cfd9e6; border-radius: 999px;
|
||||
transition: background .2s; }
|
||||
.switch-visual::before { content: ""; position: absolute; width: 20px; height: 20px;
|
||||
left: 3px; top: 3px; background: #fff; border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(16,37,63,0.25);
|
||||
transition: transform .2s; }
|
||||
.switch input:checked + .switch-visual { background: var(--primary); }
|
||||
.switch input:checked + .switch-visual::before { transform: translateX(20px); }
|
||||
.switch.warn input:checked + .switch-visual { background: var(--danger); }
|
||||
.switch input:focus-visible + .switch-visual { box-shadow: 0 0 0 3px rgba(47,138,224,.25); }
|
||||
|
||||
/* View toggle (Liste / Karte) — segmented pill, CSS-only via :has() */
|
||||
.view-toggle { display: inline-flex; border: 1px solid var(--border);
|
||||
border-radius: 999px; overflow: hidden; background: var(--surface);
|
||||
font-size: 0.85rem; font-weight: 500; }
|
||||
.view-toggle label { padding: 0.35rem 0.95rem; cursor: pointer; user-select: none;
|
||||
color: var(--muted); transition: background .15s, color .15s; }
|
||||
.view-toggle input { position: absolute; opacity: 0; pointer-events: none;
|
||||
width: 0; height: 0; }
|
||||
.view-toggle label:hover { color: var(--text); background: var(--ghost); }
|
||||
.view-toggle label:has(input:checked) { background: var(--primary); color: #fff; }
|
||||
.view-map { display: none; }
|
||||
body:has(#v_map:checked) .view-list { display: none; }
|
||||
body:has(#v_map:checked) .view-map { display: block; }
|
||||
#flats-map { height: 520px; border-radius: 10px; }
|
||||
|
||||
/* Flat detail expand */
|
||||
.flat-row { border-top: 1px solid var(--border); }
|
||||
.flat-row:first-child { border-top: 0; }
|
||||
.flat-expand-btn { width: 1.75rem; height: 1.75rem; border-radius: 999px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 1px solid var(--border); background: var(--surface);
|
||||
color: var(--muted); cursor: pointer; transition: transform .2s, background .15s; }
|
||||
.flat-expand-btn:hover { background: var(--ghost); color: var(--text); }
|
||||
.flat-expand-btn.open { transform: rotate(180deg); }
|
||||
.flat-detail { background: #fafcfe; border-top: 1px solid var(--border); }
|
||||
.flat-detail:empty { display: none; }
|
||||
|
||||
/* Normalised image gallery — every tile has the same aspect ratio */
|
||||
.flat-gallery { display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 8px; }
|
||||
.flat-gallery-tile { aspect-ratio: 4 / 3; overflow: hidden;
|
||||
border-radius: 8px; border: 1px solid var(--border);
|
||||
background: #f0f5fa; display: block; }
|
||||
.flat-gallery-tile img { width: 100%; height: 100%; object-fit: cover;
|
||||
display: block; transition: transform .3s; }
|
||||
.flat-gallery-tile:hover img { transform: scale(1.04); }
|
||||
|
||||
/* Leaflet popup — match site visual */
|
||||
.leaflet-popup-content-wrapper { border-radius: 12px; box-shadow: 0 6px 20px rgba(16,37,63,.15); }
|
||||
.leaflet-popup-content { margin: 12px 14px; min-width: 220px; color: var(--text); }
|
||||
.map-popup-title { font-weight: 600; font-size: 13px; display: inline-block; color: var(--primary); }
|
||||
.map-popup-title:hover { text-decoration: underline; }
|
||||
.map-popup-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
.map-popup-status { margin-top: 8px; }
|
||||
.map-popup-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
|
||||
.map-popup-actions .btn { padding: 0.35rem 0.7rem; font-size: 12px; }
|
||||
.map-popup-actions form { margin: 0; }
|
||||
.brand-dot {
|
||||
width: 2.5rem; height: 2.5rem; border-radius: 10px;
|
||||
background: linear-gradient(135deg, #66b7f2 0%, #2f8ae0 60%, #fbd76b 100%);
|
||||
box-shadow: 0 1px 4px rgba(47, 138, 224, 0.35);
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
overflow: hidden; flex-shrink: 0;
|
||||
}
|
||||
.brand-dot img {
|
||||
width: 88%; height: 88%; object-fit: contain; display: block;
|
||||
}
|
||||
a { color: var(--primary); }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* tab nav */
|
||||
.tab { padding: 0.7rem 0.2rem; color: var(--muted); border-bottom: 2px solid transparent;
|
||||
margin-right: 1.5rem; font-weight: 500; }
|
||||
.tab.active { color: var(--text); border-color: var(--primary); }
|
||||
.tab:hover { color: var(--text); text-decoration: none; }
|
||||
|
||||
/* auto-apply hot button */
|
||||
.btn-hot { background: linear-gradient(135deg, #ff7a85 0%, #e14a56 100%); color: white;
|
||||
box-shadow: 0 2px 6px rgba(225, 74, 86, 0.35); font-weight: 600; }
|
||||
.btn-hot:hover { filter: brightness(1.05); }
|
||||
.btn-hot.off { background: linear-gradient(135deg, #cfd9e6 0%, #99abc2 100%);
|
||||
box-shadow: 0 1px 2px rgba(16, 37, 63, 0.15); }
|
||||
|
||||
/* forensic JSON tree */
|
||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 12px; }
|
||||
details > summary { cursor: pointer; user-select: none; }
|
||||
details > summary::marker { color: var(--muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
{% block body %}{% endblock %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue