fix(library): optimize image scanning, fast raw preview extraction and lazy thumbnail loading

This commit is contained in:
2026-10-05 06:34:37 +07:00
parent 554e121e41
commit 91722861f2
12 changed files with 625 additions and 160 deletions
+93
View File
@@ -1842,3 +1842,96 @@ button.hint:disabled { cursor: default; }
transform: translate(-50%, -50%);
}
}
/* --- the phone's moderation page -----------------------------------------
Portrait, for /admin only (`.adm:not(.lib-root)`): one column, every control
full width, and nothing that asks for a pointer. The bar keeps its mark and
its name and wears its two commands as 44px icons — a finger's target, the
floor the workspace's own bars use; font-size 0 takes the word out of the
paint, not out of the button, so the accessible name and the busy state stand.
The section menu becomes the swipeable row of pills the studio's rail is, and
the shelf of albums stands over the frame it opens instead of beside it. */
@media (max-width: 860px) {
.adm:not(.lib-root) { padding: 0 12px 32px; }
.adm:not(.lib-root) .adm-bar {
flex-wrap: nowrap;
gap: 8px;
margin: 0 -12px 12px;
padding: 8px 12px;
min-height: 58px;
}
/* The count is the first thing to go: every album lists its own. */
.adm:not(.lib-root) .adm-bar .hint { display: none; }
/* The word goes, the mark stays — it is still the way home, and the name is
the title's job on this row. */
.adm:not(.lib-root) .adm-brand span { display: none; }
.adm:not(.lib-root) .adm-title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.adm:not(.lib-root) .adm-bar [data-key="adm-upload"],
.adm:not(.lib-root) .adm-bar [data-key="adm-clear"] {
flex: 0 0 auto;
width: 44px;
height: 44px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0;
}
.adm:not(.lib-root) .adm-bar [data-key="adm-upload"]::before,
.adm:not(.lib-root) .adm-bar [data-key="adm-clear"]::before { font-size: 20px; line-height: 1; }
.adm:not(.lib-root) .adm-bar [data-key="adm-upload"]::before { content: '⤒'; }
.adm:not(.lib-root) .adm-bar [data-key="adm-clear"]::before { content: '✕'; }
/* The section menu: one row of pills that scrolls, the studio rail's shape. */
.adm:not(.lib-root) .adm-side {
flex-direction: row;
flex-wrap: nowrap;
gap: 6px;
padding: 8px 12px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
.adm:not(.lib-root) .adm-side::-webkit-scrollbar { display: none; }
.adm:not(.lib-root) .adm-side-title { display: none; }
.adm:not(.lib-root) .adm-side-item {
flex: 0 0 auto;
min-height: 44px;
display: inline-flex;
align-items: center;
padding: 0 16px;
border: 1px solid var(--border);
border-radius: 999px;
font-size: 12px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.adm:not(.lib-root) .adm-side-item.on { border-color: var(--accent); }
.adm:not(.lib-root) .adm-pane { padding: 12px; }
/* Portrait: the albums stand over the frame they open, and the shelf stops
pretending to be a sidebar — it scrolls with the page. */
.adm:not(.lib-root) .adm-split { grid-template-columns: minmax(0, 1fr); }
.adm:not(.lib-root) .adm-album-list { max-height: 40vh; }
.adm:not(.lib-root) .adm-album { min-height: 44px; }
/* A destination chip is a control before it is a label. */
.adm:not(.lib-root) .adm-choice { min-height: 44px; padding: 8px 12px; }
/* The strip's tiles: two and a bit to a screen, so a thumb is worth aiming at. */
.adm:not(.lib-root) .adm-thumb { flex: 0 0 152px; }
.adm:not(.lib-root) .adm-bulk { flex-wrap: wrap; }
.adm:not(.lib-root) .adm-bulk .btn { min-height: 44px; }
/* The accounts table is wider than a phone, so it scrolls as one piece; the
rows grow to a thumb. */
.adm:not(.lib-root) .adm-table-wrap { -webkit-overflow-scrolling: touch; }
.adm:not(.lib-root) .adm-table th,
.adm:not(.lib-root) .adm-table td { padding: 9px 10px; }
.adm:not(.lib-root) .adm-table .btn { min-height: 40px; }
}
+28 -6
View File
@@ -185,6 +185,17 @@ html { scroll-behavior: smooth; scroll-padding-top: 148px; }
cursor: pointer;
}
.lp-tool:hover { color: var(--lp-text); border-color: var(--accent-line); }
/* The language button carries a globe and the code at once; which one is drawn
is the stylesheet's call (the phone block below swaps them). */
.lp-tool-globe {
display: none;
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
}
/* The theme popover borrows the workspace's .popover/.chip shell, re-skinned for
the landing palette. */
.lp-theme-pop { background: var(--lp-card); border-color: var(--lp-line); }
@@ -211,16 +222,29 @@ html { scroll-behavior: smooth; scroll-padding-top: 148px; }
@media (max-width: 1160px) {
.lp-subnav, .lp-nav .lp-wrap > .lp-btn, .lp-nav .lp-account { display: none; }
/* The bar has collapsed, so it is the phone's bar: every control is a glyph
on a finger's target. 44 is the floor the studio's own bars use — a 34px
circle with an 11px word in it was neither drawn for a thumb nor legible
on one. The language button swaps its code for a globe here; both live in
the DOM, and `aria-label` is the name either way. */
.lp-tool { width: 44px; height: 44px; font-size: 20px; }
.lp-tool-code { display: none; }
.lp-tool-globe { display: block; }
.lp-burger {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
width: 44px;
height: 44px;
background: none;
border: 0;
padding: 8px;
border: 1px solid var(--lp-line);
border-radius: 999px;
padding: 0;
cursor: pointer;
}
.lp-burger i { display: block; width: 20px; height: 2px; background: var(--lp-text); }
.lp-burger i { display: block; width: 22px; height: 2px; background: var(--lp-text); }
.lp-sheet {
display: flex;
flex-direction: column;
@@ -601,11 +625,9 @@ html { scroll-behavior: smooth; scroll-padding-top: 148px; }
}
@media (max-width: 680px) {
/* logo + theme + language + burger have to fit a 360px phone */
.lp-nav .lp-wrap { gap: 10px; }
.lp-nav .lp-wrap { gap: 8px; padding: 0 14px; }
.lp-logo { font-size: 19px; }
.lp-logo-mark { width: 24px; height: 24px; }
.lp-tool { width: 30px; height: 30px; font-size: 11px; }
.lp-burger { padding: 6px; }
.lp-sec { padding: 64px 0; }
/* The subnav is gone under 1160px, so only the notice has to be paid for. */
.lp-hero { padding: 144px 0 52px; }