From 4a6f987bf7f9d73e9dd33d225fd2d73e9bf69a88 Mon Sep 17 00:00:00 2001 From: grm Date: Wed, 16 Sep 2026 19:10:13 +0300 Subject: Merge the Layout and Design tabs into one form with a single Save The look and the layout of a blog were split over two tabs that cross-linked each other, and the Layout tab saved every toggle, move and add straight away through a dozen tiny forms. Now one Design tab holds it all, organised by part of the blog (colours & fonts, header, menu, content, side columns, footer, logo & icon) behind a sticky side index, and one Save stores the theme, every module and the menu atomically (BlogStore.SaveDesign). Module settings are edited inline; Discard changes throws the edits away; a validation error shows the form again exactly as it was sent. The image pickers no longer render the whole library four times over: a select of the newest images, and a script-driven panel that fetches the library page by page (GET /files as JSON) with lazy thumbnails. New options: posts per page, site title size, footer alignment. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/static/dashboard.css | 106 +++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 31 deletions(-) (limited to 'internal/web/static') diff --git a/internal/web/static/dashboard.css b/internal/web/static/dashboard.css index 3ef96c2..32561bf 100644 --- a/internal/web/static/dashboard.css +++ b/internal/web/static/dashboard.css @@ -195,28 +195,25 @@ table.files td.name { word-break: break-all; } table.files .copy { width: 100%; min-width: 11em; padding: 0.3em 0.4em; margin: 0; font-family: var(--mono); font-size: 0.75em; } .pager { margin: 0 0 1.4em; text-align: center; font-size: 0.92em; } .pager a, .pager span { margin: 0 0.8em; } -.imagepick { margin-top: 0.8em; padding-top: 0.6em; border-top: 1px solid var(--border); } -.imagepick > label { margin-bottom: 0.3em; } -.imagepick .picks { display: flex; flex-wrap: wrap; gap: 0.7em; margin: 0.4em 0 0; } -.imagepick .pick { width: 120px; margin: 0; padding: 0.4em; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); text-align: center; font-size: 0.75em; font-weight: normal; cursor: pointer; } -.imagepick .pick:hover { border-color: var(--border-strong); background: var(--surface-2); } -.imagepick .pick input { display: block; width: auto; margin: 0 auto 0.3em; } -.imagepick .pick img, .imagepick .pick .swatch { display: block; width: 100%; height: 64px; margin: 0 auto 0.3em; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-2); } -.imagepick .pick img { object-fit: contain; } -.imagepick .pick .swatch { line-height: 64px; font-size: 1.6em; color: var(--muted); } -.imagepick .pick .name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.imagepick .pick input:checked ~ img, .imagepick .pick input:checked ~ .swatch { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); } - -/* ---- design page ---------------------------------------------------------- */ -.jumps { display: flex; flex-wrap: wrap; gap: 0.4em; margin: 0 0 1.2em; font-size: 0.85em; } -.jumps a { display: inline-block; padding: 0.2em 0.75em; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--text); text-decoration: none; } -.jumps a:hover { border-color: var(--accent); color: var(--accent); } +/* ---- design page: side index, one form, module rows, image pickers ---------- */ +.design { display: grid; grid-template-columns: 11em minmax(0, 1fr); gap: 0 1.4em; align-items: start; } +.sidenav { position: sticky; top: 1em; display: flex; flex-direction: column; gap: 0.15em; font-size: 0.9em; } +.sidenav a { padding: 0.35em 0.7em; border-radius: var(--radius-sm); color: var(--muted); text-decoration: none; font-weight: 500; } +.sidenav a:hover { background: var(--surface); color: var(--text); } +.sidenav a.active { background: var(--accent-soft); color: var(--accent-hover); } +.design .card h3 { margin-top: 1.4em; padding-top: 0.9em; border-top: 1px solid var(--border); } +.design .card h3:first-of-type { margin-top: 0.8em; } +.design .card h2 + h3 { margin-top: 0; padding-top: 0; border-top: 0; } +.design h4 { font-size: 0.9em; font-weight: 600; margin: 1em 0 0.2em; } +/* what only makes sense with, or without, the script */ +form.js .nojs { display: none !important; } +form:not(.js) .jsonly { display: none !important; } .presets { display: flex; flex-wrap: wrap; gap: 0.8em; } .presets .preset { width: 8.5em; margin: 0; padding: 0.55em 0.45em; background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 0.8em; font-weight: 500; text-align: center; } .presets .preset:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); } .presets .sw { display: inline-block; width: 22%; height: 2.2em; margin: 0 1%; border: 1px solid rgba(0,0,0,0.15); border-radius: 3px; vertical-align: middle; } .presets .pname { display: block; margin-top: 0.4em; } -/* layout page: the column diagram, area cards with their module tables */ +/* the column diagram */ .lay { margin: 0 0 1em; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface-2); font-size: 0.75em; font-weight: 500; text-align: center; overflow: hidden; } .lay-row { padding: 0.5em; background: var(--text); color: #fff; } .lay-cols { display: flex; padding: 0.5em; gap: 0.3em; } @@ -226,38 +223,85 @@ table.files .copy { width: 100%; min-width: 11em; padding: 0.3em 0.4em; margin: .lay-col.off { text-decoration: none; } .lay-col.empty { background: transparent; } .row input[type=number] { width: 6em; } -.cardhead { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5em 1em; } -.cardhead h2 { margin: 0 0 0.6em; } -.cardhead .switch { margin: 0 0 0.6em auto; } -.card.area-off { border-style: dashed; box-shadow: none; } -.card.area-off .modules, .card.area-off .addmod { opacity: 0.6; } -table.modules { margin: 0.5em 0 1em; } -table.modules td.order, table.modules td.actions { width: 1%; } -table.modules th { width: 10em; } -.addmod { border-top: 1px solid var(--border); padding-top: 0.6em; } -.addmod label, .add label { display: inline-block; margin: 0 0.8em 0.4em 0; min-width: 14em; } -.addmod select, .add select, .add input { width: auto; min-width: 14em; } -.addmod button, .add button { vertical-align: bottom; margin-bottom: 0.4em; } +/* module and menu rows */ +.arealist { margin: 0.6em 0 0; } +.arealist.area-off .modlist, .arealist.area-off .addmod { opacity: 0.55; } +.modlist { margin: 0.4em 0; } +.modlist:empty { display: none; } +.modrow { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4em 0.7em; padding: 0.45em 0.6em; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); margin: 0 0 0.4em; } +.modrow .order { display: inline-flex; gap: 0.2em; } +.modrow .order .pos { width: 3.6em; margin: 0; padding: 0.2em 0.3em; font-size: 0.85em; } +.modrow .modname { font-weight: 500; } +.modrow .tag { margin-left: 0; } +.modrow .modact { margin-left: auto; } +.modrow .modact label.check { display: inline; margin: 0; font-size: 0.85em; } +.modrow .modset { flex-basis: 100%; font-size: 0.95em; } +.modrow .modset summary { cursor: pointer; color: var(--muted); font-size: 0.9em; } +.modrow .modset summary .modsum { font-style: italic; } +.modrow .modset[open] summary { color: var(--text); } +.modrow .modset label { margin: 0.5em 0; } +.modrow .modset .row { gap: 0 1em; } +.modrow .modset .row label { flex: 0 1 14em; min-width: 10em; } +.modrow .modset p:last-child { margin-bottom: 0.3em; } +.modrow .linkfields { display: inline-flex; flex-wrap: wrap; gap: 0.4em; flex: 1 1 14em; } +.modrow .linkfields input { flex: 1 1 8em; width: auto; margin: 0; padding: 0.25em 0.45em; font-size: 0.9em; } +.addmod label { display: inline-block; margin: 0.3em 0 0.4em; min-width: 14em; } +.addmod select { width: auto; min-width: 14em; } .addrow { margin-top: 1em; border-top: 1px solid var(--border); padding-top: 0.6em; margin-bottom: 0; } +.addrow h3 { margin: 0.5em 0 0.2em; padding: 0; border: 0; } +.addrow select, .addrow input { width: auto; min-width: 14em; } +.addrow button { vertical-align: bottom; margin-bottom: 0.4em; } textarea.mono { font-size: 0.9em; } .hoverpick { margin: 0.8em 0; } .hoverpick > input { width: auto; margin: 0 0.4em 0 0; vertical-align: middle; } .hoverpick > label.check { display: inline; margin: 0; } .hoverpick .hover-color { margin: 0.4em 0 0 1.6em; } .hoverpick > input:not(:checked) ~ .hover-color { display: none; } -.savebar { position: sticky; bottom: 0; margin: 0; padding: 0.8em 0; background: var(--bg); border-top: 1px solid var(--border-strong); display: flex; gap: 0.8em; align-items: center; } +/* image fields: the chosen image, the select (or the library button) and an upload */ +.imagepick { margin: 0.9em 0 0; } +.imagepick > label { margin-bottom: 0.3em; } +.pickrow { display: flex; gap: 1em; align-items: flex-start; } +.pickrow .preview { flex: 0 0 auto; } +.pickrow .preview img, .pickrow .preview .swatch { display: block; width: 96px; height: 72px; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-2); object-fit: contain; } +.pickrow .preview .swatch { line-height: 72px; text-align: center; font-size: 1.6em; color: var(--muted); } +.pickrow .pickctl { flex: 1 1 auto; min-width: 0; } +.pickrow .pickctl select { max-width: 24em; margin: 0 0 0.5em; } +.pickrow .pickctl .upload { margin: 0.5em 0 0; font-size: 0.85em; } +.pickrow .pickctl .upload input { max-width: 24em; } +.picker { position: fixed; inset: 0; z-index: 20; background: rgba(16, 24, 40, 0.45); padding: 3vh 1em; overflow: auto; } +.picker-box { max-width: 860px; margin: 0 auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 30px rgba(16, 24, 40, 0.25); padding: 1em 1.2em; } +.picker-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6em; margin-bottom: 0.8em; } +.picker-head input { flex: 1 1 12em; width: auto; margin: 0; padding: 0.4em 0.6em; } +.picker .picks { display: flex; flex-wrap: wrap; gap: 0.7em; } +.picker .pick { width: 120px; margin: 0; padding: 0.4em; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); text-align: center; font-size: 0.75em; font-weight: normal; cursor: pointer; } +.picker .pick:hover { border-color: var(--accent); background: var(--surface-2); color: var(--text); } +.picker .pick img { display: block; width: 100%; height: 64px; margin: 0 auto 0.3em; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-2); object-fit: contain; } +.picker .pick .name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.picker .pager { margin: 0.8em 0 0; } +.picker .pager button[disabled] { opacity: 0.4; cursor: default; } +.savebar { position: sticky; bottom: 0; z-index: 5; margin: 0; padding: 0.8em 0; background: var(--bg); border-top: 1px solid var(--border-strong); display: flex; flex-wrap: wrap; gap: 0.8em; align-items: center; } +.savebar .unsaved { font-size: 0.85em; font-weight: 600; color: #92400e; background: #fef3c7; border: 1px solid #fcd34d; border-radius: 999px; padding: 0.15em 0.7em; } +.savebar .viewlink { margin-left: auto; font-size: 0.9em; } details.reset { margin-top: 1.6em; } /* ---- login --------------------------------------------------------------- */ .login-brand { text-align: center; font-weight: 700; font-size: 1.2em; letter-spacing: -0.01em; margin: 2.5em 0 1em; } +@media (max-width: 900px) { + .design { display: block; } + .sidenav { position: static; flex-direction: row; flex-wrap: wrap; margin: 0 0 1em; gap: 0.3em; } + .sidenav a { border: 1px solid var(--border); background: var(--surface); } +} @media (max-width: 700px) { body { font-size: 15px; } .cols { grid-template-columns: 1fr; gap: 1.2em; } .row { gap: 0; } .row label { flex-basis: 100%; } - .imagepick .pick { width: 46%; } + .picker .pick { width: 46%; } .presets .preset { width: 46%; } + .sidenav { flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; margin: 0 -1em 1em; padding: 0 1em; } + .pickrow { flex-wrap: wrap; } + .modrow .modact label.check { font-size: 0.8em; } .topbar .links { margin-left: 0; flex-basis: 100%; gap: 1em; } .blogbar .topbar-inner { display: block; } .blogbar .blogname { display: block; max-width: none; padding-bottom: 0.3em; } -- cgit v1.2.3