Fandom count fix + Explore 3-pane workspace + modal rail layout #131

Merged
bvandeusen merged 3 commits from dev into main 2026-06-26 08:24:27 -04:00
3 changed files with 56 additions and 28 deletions
Showing only changes of commit 1728b43167 - Show all commits
+23 -11
View File
@@ -1,7 +1,8 @@
<template>
<section v-if="img" class="fc-meta" aria-label="Image details">
<!-- #4a: dimensions / size / type already on the payload, shown nowhere
until now. -->
<!-- #4a: dimensions / size / type sit as a compact top block alongside a
small save action (operator-asked 2026-06-26) meta on the left, the
download control shrunk to a floppy-disk icon on the right. -->
<dl class="fc-meta__grid">
<div v-if="img.width && img.height" class="fc-meta__item">
<dt>Dimensions</dt><dd>{{ img.width }} × {{ img.height }}</dd>
@@ -14,21 +15,27 @@
</div>
</dl>
<!-- #4b: split Download default Download, chevron Copy link. Image-to-
clipboard is OFF the table on the plain-HTTP origin (rule 95), so the
secondary action copies the link TEXT, which works everywhere. -->
<v-btn-group divided density="comfortable" variant="tonal" color="accent" rounded="pill">
<v-btn prepend-icon="mdi-download" @click="download">Download</v-btn>
<!-- #4b: floppy-disk save icon = download; the kebab menu keeps Copy link.
Image-to-clipboard is OFF the table on the plain-HTTP origin (rule 95),
so the secondary action copies the link TEXT, which works everywhere. -->
<div class="fc-meta__actions">
<v-btn
icon="mdi-content-save" size="small" variant="tonal" color="accent"
aria-label="Download image" title="Download" @click="download"
/>
<v-menu location="bottom end">
<template #activator="{ props }">
<v-btn v-bind="props" icon="mdi-chevron-down" aria-label="More download options" />
<v-btn
v-bind="props" icon="mdi-dots-vertical" size="small" variant="text"
aria-label="More download options"
/>
</template>
<v-list density="compact">
<v-list-item prepend-icon="mdi-link-variant" title="Copy link" @click="copyLink" />
<v-list-item prepend-icon="mdi-download" title="Download" @click="download" />
<v-list-item prepend-icon="mdi-content-save" title="Download" @click="download" />
</v-list>
</v-menu>
</v-btn-group>
</div>
</section>
</template>
@@ -83,11 +90,16 @@ async function copyLink () {
<style scoped>
.fc-meta {
padding: 14px 16px 0;
display: flex; flex-direction: column; gap: 10px;
display: flex; align-items: flex-start; gap: 12px;
}
.fc-meta__grid {
flex: 1 1 auto; min-width: 0;
display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 0;
}
.fc-meta__actions {
flex: 0 0 auto;
display: flex; align-items: center; gap: 2px;
}
.fc-meta__item { display: flex; flex-direction: column; }
.fc-meta__item dt {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em;
+27 -6
View File
@@ -73,12 +73,17 @@
</div>
<aside v-if="modal.current" class="fc-viewer__side">
<ImageMetaBar />
<ProvenancePanel />
<TagPanel />
<!-- Non-blocking: fetches its own similar set after the modal is up;
collapses silently if empty/slow/failed (see RelatedStrip). -->
<RelatedStrip />
<!-- Meta + provenance + tags + suggestions scroll together here -->
<div class="fc-viewer__side-main">
<ImageMetaBar />
<ProvenancePanel />
<TagPanel />
</div>
<!-- while Related is PINNED to the bottom of the rail so it stays
reachable no matter how long Tags/Suggestions run (operator-asked
2026-06-26). Non-blocking: fetches its own similar set; collapses
silently (and takes no footer space) if empty/slow/failed. -->
<RelatedStrip class="fc-viewer__related" />
</aside>
</div>
</div>
@@ -309,6 +314,18 @@ function nextFrame() {
width: var(--fc-side-w); flex-shrink: 0;
background: rgb(var(--v-theme-surface));
border-left: 1px solid rgb(var(--v-theme-surface-light));
/* Flex column: a scrolling main area + a pinned Related footer. */
display: flex; flex-direction: column; min-height: 0;
}
.fc-viewer__side-main {
flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
.fc-viewer__related {
/* Pinned to the bottom; capped so a tall strip can't swallow the rail —
it scrolls internally past the cap. RelatedStrip's own border-top draws
the divider; it self-collapses (no space) when there's nothing to show. */
flex: 0 0 auto;
max-height: 45%;
overflow-y: auto;
}
@@ -339,6 +356,10 @@ function nextFrame() {
border-left: none;
border-top: 1px solid rgb(var(--v-theme-surface-light));
}
/* The whole body scrolls on mobile — don't nest scrolls or pin Related; let
the rail flow naturally (Related lands at the end). */
.fc-viewer__side-main { flex: none; overflow: visible; }
.fc-viewer__related { max-height: none; overflow: visible; }
/* Re-center the prev/next arrows over the 55vh image band (their base
top:50% would land on the scrolling panel); next uses the full width
now that the panel is below, not beside. Close + integrity badge keep
@@ -12,10 +12,9 @@
No suggestions above threshold.
</div>
<!-- Scroll-capped so a long suggestion set (the General bucket can run to
dozens) scrolls WITHIN this box instead of stretching the right rail
and shoving the Related strip below the fold. Mirrors the
ProvenancePanel cards/attachments treatment. -->
<!-- Flows in the rail's main scroll area; Related is pinned to the bottom
of the rail (ImageViewer side layout), so a long suggestion set no
longer needs an internal scroll cap to keep Related reachable. -->
<div v-else class="fc-suggestions__list">
<SuggestionsCategoryGroup
v-for="cat in peopleCats" :key="cat"
@@ -137,13 +136,9 @@ async function onRemoveAlias(s) {
margin-bottom: 8px;
}
.fc-suggestions__list {
/* Cap the suggestion run so it scrolls internally rather than pushing the
Related strip off the bottom of the rail. Hairline scrollbar matching
ProvenancePanel's capped regions. */
max-height: 320px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgb(var(--v-theme-surface-light)) transparent;
/* No internal scroll cap: the rail now scrolls suggestions in its single
main scroll area while Related is pinned to the bottom (ImageViewer side
layout), so suggestions flow naturally without a nested scrollbar. */
padding-right: 4px;
}
.fc-suggestions__skeleton { display: flex; flex-direction: column; gap: 8px; }