Files
photog/static/style.css
T
puttaalu 15822ed227 Add swipe navigation to the photo detail page
Swipe left for older, right for newer, matching the existing arrow keys.
The image tracks the finger and slides out on commit; the exit animation
covers the page load, so this stays plain static-site navigation.

The keyboard handler moves out of the template into static/photo-nav.js,
which the existing //go:embed of static/ picks up with no generator
changes.

Swipes starting within 24px of a screen edge are left alone — that strip
is the browser's own back-gesture. Only touch pointers are handled, so
desktop drag-to-save still works, and the axis locks after 10px so
vertical drags stay scrolls. With no neighbour that way the image
rubber-bands instead of navigating.

Also prefetch the neighbour medium JPEGs, not just their HTML, or the
next page still blocks on a fresh image request.

The keyboard handler now bails on modifier keys; it previously hijacked
Alt+ArrowLeft, which is browser-back on Linux and Windows.
2026-08-19 23:24:42 +02:00

378 lines
7.0 KiB
CSS

:root {
--bg: #1d2021;
--bg0: #282828;
--bg1: #3c3836;
--bg2: #504945;
--fg: #ebdbb2;
--fg2: #d5c4a1;
--fg3: #bdae93;
--fg4: #a89984;
--aqua: #8ec07c;
--green: #98971a;
--blue: #458588;
--line: #3c3836;
--serif: 'Crimson Pro', Georgia, serif;
--mj: 'Manjari', sans-serif;
}
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--fg);
font-family: var(--serif);
min-height: 100vh;
}
/* ── Profile header ────────────────────────────────────────── */
.profile-hdr {
border-bottom: 1px solid var(--line);
padding: 28px 20px 22px;
}
.profile-inner {
max-width: 935px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 36px;
}
.avatar-ring {
width: 84px;
height: 84px;
border-radius: 50%;
border: 2px solid var(--bg2);
overflow: hidden;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg1);
}
.avatar-ring img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.avatar-initials {
font-size: 32px;
font-weight: 700;
color: var(--fg3);
font-family: var(--serif);
}
.pinfo h1 {
font-family: var(--serif);
font-size: 26px;
font-weight: 700;
color: var(--green);
margin-bottom: 3px;
}
.handle {
font-size: 12px;
color: var(--blue);
font-family: monospace;
margin-bottom: 10px;
letter-spacing: 0.02em;
}
.pcount {
font-size: 15px;
color: var(--fg3);
margin-bottom: 8px;
}
.pcount strong {
color: var(--fg);
font-weight: 600;
}
.pbio {
font-size: 15px;
color: var(--fg2);
margin-bottom: 10px;
line-height: 1.7;
max-width: 280px;
font-style: italic;
}
.ml {
font-family: var(--mj);
font-style: normal;
font-weight: 400;
font-size: 17px;
line-height: 1.5;
}
.rss-a {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--aqua);
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.05em;
font-family: monospace;
}
.rss-a:hover {
color: var(--fg2);
}
/* ── Photo grid ────────────────────────────────────────────── */
.grid-wrap {
max-width: 935px;
margin: 0 auto;
padding: 3px 0;
}
.pgrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3px;
}
.ptile {
aspect-ratio: 1;
overflow: hidden;
display: block;
background: var(--bg1);
}
.ptile img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.3s ease;
}
.ptile:hover img {
transform: scale(1.05);
}
/* ── Pager ─────────────────────────────────────────────────── */
.pager {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 20px 8px;
font-family: monospace;
font-size: 13px;
letter-spacing: 0.04em;
}
.pager a {
color: var(--aqua);
text-decoration: none;
}
.pager a:hover {
color: var(--fg2);
}
.pager-newer { margin-right: auto; }
.pager-older { margin-left: auto; }
/* ── Single photo page ─────────────────────────────────────── */
.photo-page {
max-width: 935px;
margin: 0 auto;
padding: 16px 20px;
}
.photo-nav {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 8px 0;
margin-bottom: 16px;
font-family: var(--serif);
font-size: 15px;
}
.photo-nav a {
color: var(--fg3);
text-decoration: none;
}
.photo-nav a:hover {
color: var(--fg);
}
.photo-nav-neighbours {
color: var(--fg3);
}
.dlayout {
display: flex;
align-items: stretch;
border: 1px solid var(--line);
}
.dimg-wrap {
flex: 1;
min-width: 0;
overflow: hidden;
background: var(--bg1);
display: flex;
align-items: center;
justify-content: center;
}
.dimg-wrap a {
display: block;
max-width: 100%;
max-height: 85vh;
}
.dimg-wrap img {
display: block;
max-width: 100%;
max-height: 85vh;
width: auto;
height: auto;
}
/* Swipe navigation. pan-y claims the horizontal axis for the gesture handler
while leaving vertical scrolling native. will-change is scoped to the two
transient states so the image is not permanently promoted to its own layer. */
.dimg-wrap {
touch-action: pan-y;
}
.dimg-wrap.dragging {
user-select: none;
-webkit-user-select: none;
}
.dimg-wrap.dragging img {
transition: none;
will-change: transform;
}
.dimg-wrap.animating img {
transition: transform 180ms ease-out, opacity 180ms ease-out;
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
.dimg-wrap.animating img { transition: none; }
}
.dsidebar {
width: 220px;
flex-shrink: 0;
padding: 18px;
border-left: 1px solid var(--line);
background: var(--bg0);
}
.dauthor {
display: flex;
align-items: center;
gap: 10px;
padding-bottom: 14px;
border-bottom: 1px solid var(--line);
margin-bottom: 14px;
}
.mini-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid var(--bg2);
background: var(--bg1);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
color: var(--fg3);
font-family: var(--serif);
flex-shrink: 0;
}
.dauthor-name {
font-size: 14px;
font-weight: 600;
color: var(--fg);
font-family: var(--serif);
}
.dcaption {
font-family: var(--serif);
font-size: 17px;
font-style: italic;
color: var(--fg);
margin-bottom: 10px;
line-height: 1.6;
}
.ddate {
font-size: 11px;
color: var(--fg4);
font-family: monospace;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.dfull {
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid var(--line);
}
.dfull a {
font-size: 13px;
font-family: monospace;
color: var(--aqua);
text-decoration: none;
letter-spacing: 0.04em;
}
.dfull a:hover {
color: var(--fg2);
}
/* ── Site footer ───────────────────────────────────────────── */
.site-footer {
max-width: 935px;
margin: 48px auto 0;
padding: 18px 20px 28px;
border-top: 1px solid var(--line);
text-align: center;
font-size: 12px;
color: var(--fg4);
font-family: monospace;
letter-spacing: 0.04em;
}
/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 600px) {
.profile-inner { gap: 18px; }
.avatar-ring { width: 60px; height: 60px; }
.avatar-initials { font-size: 22px; }
.pinfo h1 { font-size: 20px; }
.pgrid { grid-template-columns: repeat(2, 1fr); }
.dlayout { flex-direction: column; }
.dsidebar { width: 100%; border-left: none; border-top: 1px solid var(--line); }
}