Files
kgva/assets/css/custom.css
T
kgva CMS 113a954b16 feat: sticky locked-image thumbnail while scrolling; two-click preview-then-open on the portfolio index
the locked image now gets a small pinned copy (18rem, same width as the text column) that appears once the real preview scrolls out of view and disappears again once the text runs out — fixed-position + scroll-driven rather than CSS sticky, since the filebrowser box is only ever as tall as its own content, not the much taller text next to it.\n\nalso scoped the existing click-outside-unfreezes-the-lock listener to clicks inside .filebrowser only, since with listings now starting locked, a click to read or select a word in the text would otherwise silently kill the sticky preview.\n\nportfolio index: rows there are real links (href + data-img together), so a single click used to jump straight into the project — the only way to preview a different one first was hovering, which does nothing for touch and is now also blocked while something is locked. first click on a different project now just previews it (like hover used to); a second click on the same row, or clicking the preview image itself, opens it.
2026-08-11 19:12:43 +00:00

517 lines
17 KiB
CSS

/* ══════════════════════════════════════════════════════════════
custom.css — extends shibui WITHOUT touching the theme.
- top bar reads like a terminal prompt: "/ karimgabrielevarano_ ls"
- clicking "ls" lists the pages, stacked, inverted on hover
- only the work index breaks the narrow column
remove this file → original shibui look returns.
══════════════════════════════════════════════════════════════ */
/* plain Iosevka instead of the theme's "Iosevka Curly Slab" — same family,
calmer letterforms (straight $, no slab foot on digits). self-hosted,
subset to exactly the characters used on the site (fonttools --unicodes),
so it's smaller than the curly-slab cut it replaces despite covering en+de
plus the shell/tree symbols (→ ← … · ▶ ⛶ ☕ ✕ ❚ box-drawing). overrides the
theme's --font-family-* vars below (custom.css loads after site.css). */
@font-face {
font-family: "Iosevka";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/iosevka-400.woff2") format("woff2");
}
@font-face {
font-family: "Iosevka";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/iosevka-700.woff2") format("woff2");
}
:root {
--font-family-mono: "Iosevka", ui-monospace, monospace;
--font-family-sans: "Iosevka", ui-monospace, monospace;
}
/* self-hosted serif, used only for the project-info side panel */
@font-face {
font-family: "Libre Baskerville";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/libre-baskerville-400.woff2") format("woff2");
}
@font-face {
font-family: "Libre Baskerville";
font-style: italic;
font-weight: 400;
font-display: swap;
src: url("/fonts/libre-baskerville-400italic.woff2") format("woff2");
}
@font-face {
font-family: "Libre Baskerville";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/libre-baskerville-700.woff2") format("woff2");
}
:root { --font-family-serif: "Libre Baskerville", Georgia, serif; }
/* ─────────────────────── SHELL PROMPT ─────────────────────── */
.prompt {
font-family: var(--font-family-mono);
display: flex;
/* stay on ONE line: when the command gets too wide, clip the left (older)
part terminal-style and keep the caret/right end visible (shell.js scrolls
it to the end). previously flex-wrap:wrap dropped to a 2nd line and bugged. */
flex-wrap: nowrap;
align-items: flex-start;
gap: 0;
line-height: 1.5;
overflow: hidden;
white-space: nowrap;
}
.prompt__user { font-weight: 700; }
.prompt__path { color: var(--color-text-muted); }
/* breadcrumb segments look like plain prompt text at rest, fill on hover */
.prompt__seg {
margin-left: 0;
border-bottom: none;
background: none;
color: inherit;
}
.prompt__seg:hover {
background: var(--color-text-primary);
color: var(--color-bg-primary);
border-bottom: none;
}
/* the typeable command — looks like shell text, native caret hidden so our
blinking "_" is the cursor */
.prompt__input {
margin-left: 0.6ch;
min-width: 0;
font-weight: 700;
color: var(--color-text-primary);
white-space: pre;
outline: none;
caret-color: transparent;
}
/* a command "typed" by the file browser on hover/freeze — dimmed so it reads
as a hint, not something you committed. any real keystroke discards it. */
.prompt__input.is-ghost { color: var(--color-text-muted); font-weight: 400; }
/* blinking terminal cursor, sits right after the typed text */
.caret {
font-weight: 700;
color: var(--color-text-primary);
animation: blink 1.1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
/* always-open file menu (the shell's "ls") — only shown on the home page;
on subpages it stays in the DOM (hidden) so typed commands still resolve */
/* stays the file list's normal width even when .topbar is widened (below) to
line up the shell/lang-switch with a wider info panel elsewhere — the home
menu was never meant to stretch, only the prompt row + toggle were */
.menu { margin: var(--spacing-sm) 0 0; max-width: var(--container-width); }
body:not(.home) .menu { display: none; }
/* easter-egg output line — takes no space until it actually has output */
.prompt__out {
font-family: var(--font-family-mono);
color: var(--color-text-muted);
margin: 0;
white-space: pre-wrap;
}
.prompt__out:not(:empty) { margin: 0.35rem 0 0; }
/* ─────────────────────── HOME IMAGE ─────────────────────── */
/* sits low enough that opening the ls overlay never moves it */
.home .hero__img { margin: 2rem 0 0; text-align: left; }
.hero__img img { width: 100%; height: auto; margin: 0; }
/* ─────────────────── WORK INDEX (ls -l style) ─────────────────── */
.tui { font-family: var(--font-family-mono); }
.tui__cmd { margin: 0; color: var(--color-text-muted); }
.tui__sym { color: var(--color-text-muted); }
.tui__total { margin: 0 0 0.6rem; }
.tui__ls { list-style: none; margin: 0; padding: 0; }
.tui__ls li { margin: 0; }
.tui__ls a {
display: grid;
grid-template-columns: 11ch 5ch 1fr;
gap: 0 1ch;
margin: 0;
padding: 0 0.25em;
border-bottom: none;
color: var(--color-text-primary);
white-space: nowrap;
}
.tui__perm,
.tui__date { color: var(--color-text-muted); }
.tui__ls a:hover,
.tui__ls a.is-selected {
background: var(--color-text-primary);
color: var(--color-bg-primary);
}
.tui__ls a:hover .tui__perm,
.tui__ls a:hover .tui__date,
.tui__ls a:hover .tui__name,
.tui__ls a.is-selected .tui__perm,
.tui__ls a.is-selected .tui__date,
.tui__ls a.is-selected .tui__name { color: var(--color-bg-primary); }
/* preview pane (ranger-style) below the listing */
.tui__preview { margin: 1.25rem 0 0; }
.tui__preview img {
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
height: auto;
margin: 0;
display: block;
transition: opacity 0.25s ease;
}
/* portfolio index only (not project pages): the big preview is itself a
second way into the already-previewed project, next to clicking its row
again */
.filebrowser:not(.tui--files) .tui__preview img { cursor: pointer; }
/* project page: image files (no date column), show full image (contain) */
.tui--files .tui__ls a { grid-template-columns: 11ch 1fr; }
/* long listings (>8 entries) scroll instead of pushing the preview down */
.tui--files .tui__ls {
max-height: 12em; /* ~8 rows at line-height 1.5 */
overflow-y: auto;
}
.tui--files .tui__preview img {
aspect-ratio: auto; /* keep each image's own ratio — no letterboxing */
width: auto;
max-width: 100%;
max-height: 80vh;
height: auto;
object-fit: contain;
background: none;
}
/* info.md text preview */
.tui__text { font-size: var(--font-size-base); text-align: left; }
.tui__text p { text-align: left; }
.tui__text > :first-child { margin-top: 0; }
.tui__text > :last-child { margin-bottom: 0; }
/* ─────────────────── PROJECT INFO PANEL ─────────────────── */
/* info.md (the project description / about text) lives outside the
filebrowser now — on wide viewports it sits to the right of the file
list, clearing the "$ ls" / "total N" shell lines up top; on narrow ones
there's no room for two columns, so it just stacks below instead.
the file list itself always stays exactly where the single centered
column normally sits — same position with or without an info panel,
so nothing shifts around depending on whether a page has text. */
.proj-layout { display: block; }
.proj-info {
margin: 1.5rem 0 0;
font-family: var(--font-family-serif);
font-size: 0.85em;
line-height: 1.6;
text-align: justify;
/* justified needs hyphenation, otherwise a narrow column like this one gets
ugly gaps between words; :lang() on <html> (set per-page by Hugo) picks
the right hyphenation dictionary for en vs. de automatically. */
hyphens: auto;
-webkit-hyphens: auto;
text-wrap: pretty;
}
.proj-info > :first-child { margin-top: 0; }
.proj-info > :last-child { margin-bottom: 0; }
@media (min-width: 1100px) {
.proj-layout {
width: 100vw;
margin-left: calc(50% - 50vw);
/* pushes the file list back out to the same spot the narrow centered
column always occupies — this is a fixed position, not a re-centered
pair, so it doesn't move depending on whether .proj-info exists */
padding-left: calc((100vw - var(--container-width)) / 2);
padding-right: 1.5rem;
box-sizing: border-box;
display: flex;
align-items: flex-start;
gap: 1.5rem;
}
.proj-layout .filebrowser { flex: 0 0 var(--container-width); }
.proj-info {
flex: 0 0 18rem;
margin-top: 3.6rem; /* clear the two shell lines above the file list */
}
/* shell line + lang-switch always stretch out to the file-list + info-panel
width, on every page — not just ones that happen to render an info
panel — so the toggle sits in the same spot site-wide */
.topbar {
width: calc(var(--container-width) + 1.5rem + 18rem);
}
}
/* small pinned copy of the locked image — appears at the left edge (same
spot the real preview normally sits) once that preview has scrolled out
of view, and disappears again once the text next to it runs out. width
matches .proj-info so the two columns read as a pair. fixed, not sticky:
the file list's own box is only as tall as its own content, not the much
taller text next to it, so sticky positioning has nothing to be bounded
by — a scroll-driven fixed element sidesteps that entirely. */
.proj-sticky {
position: fixed;
top: 50%;
left: calc((100vw - var(--container-width)) / 2);
transform: translateY(-50%);
width: 18rem;
z-index: 5;
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease;
pointer-events: none;
}
.proj-sticky.is-visible { opacity: 1; visibility: visible; }
.proj-sticky img {
width: 100%;
height: auto;
max-height: 60vh;
object-fit: contain;
display: block;
margin: 0;
}
@media (max-width: 1099.98px) {
.proj-sticky { display: none; }
}
/* prev/next as shell "cd" commands — same vocabulary as the rest of the site */
.proj-nav {
font-family: var(--font-family-mono);
margin: 1.5rem 0 0;
display: flex;
gap: 1.5ch;
}
.proj-nav a[rel="next"] { margin-left: auto; } /* → always pinned right */
.proj-nav a {
border-bottom: none;
color: var(--color-text-muted);
}
.proj-nav a:hover {
background: var(--color-text-primary);
color: var(--color-bg-primary);
border-bottom: none;
}
/* studies path → clickable taxonomy filters, but indistinguishable from plain
text (no underline, no hover, inherits the surrounding colour) */
.proj-path__seg,
.proj-path__seg:hover {
margin-left: 0;
border-bottom: none;
background: none;
color: inherit;
}
.proj-path__seg--last { font-weight: 700; }
/* secret retro games (doom, simcity, pacman, tetris) — each runs inline as
<name>.sh inside a fake .<name> folder, same file-listing chrome as any
other page. sized to the classic 4:3 DOS/arcade aspect ratio. */
.game-player {
width: 100%;
aspect-ratio: 4 / 3;
background: #000;
}
.game-player iframe {
width: 100%;
height: 100%;
border: 0;
display: block;
}
/* about: terminal-style metadata / contact list */
.meta {
display: grid;
grid-template-columns: max-content 1fr;
column-gap: 1.5ch;
row-gap: 0.15rem;
font-family: var(--font-family-mono);
margin: var(--spacing-base) 0 0;
}
.meta dt { color: var(--color-text-muted); }
.meta dd { margin: 0; }
.meta a { margin-left: 0; }
/* `dark` / `light` theme toggle — overrides the theme's colour variables */
html.dark {
color-scheme: dark;
--color-bg-primary: hsl(0 0% 8%);
--color-bg-secondary: hsl(0 0% 12%);
--color-border: hsl(0 0% 22%);
--color-selection-bg: hsl(0 0% 32%);
--color-text-primary: hsl(0 0% 92%);
--color-text-muted: hsl(0 0% 58%);
--color-text-code: hsl(0 0% 78%);
}
/* `sl` — a steam locomotive that rolls across the screen */
.sl {
position: fixed;
top: 38%;
left: 0;
margin: 0;
z-index: 9998;
white-space: pre;
font-family: var(--font-family-mono);
font-size: 12px;
line-height: 1;
color: var(--color-text-primary);
pointer-events: none;
}
/* boot splash — systemd/Arch-style log, but in the site's own look:
light background, same mono font, constrained to the site column width */
#boot { display: none; }
html.booting #boot {
display: block;
position: fixed;
inset: 0;
z-index: 10000;
overflow-y: auto;
scrollbar-width: none;
padding: 0.8rem 0;
background: var(--color-bg-primary);
cursor: none;
}
html.booting #boot::-webkit-scrollbar { display: none; }
.boot__log {
width: 100%;
max-width: var(--container-width);
margin: 0 auto;
padding: 0 1.5rem;
border: 0;
background: none;
box-shadow: none;
border-radius: 0;
font-family: var(--font-family-mono);
font-size: var(--font-size-small);
line-height: 1.6;
color: var(--color-text-primary);
white-space: pre-wrap;
word-break: break-word;
}
.boot__line { white-space: pre-wrap; }
.boot__ok { font-weight: 700; }
.boot__dim { color: var(--color-text-muted); }
/* text isn't selectable anywhere — except the command line, which you type in */
body {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.prompt__input {
-webkit-user-select: text;
-moz-user-select: text;
user-select: text;
}
/* dotted content + footer links: unchanged at rest, invert to black on hover.
include the header language switch so en/de behave like every other link. */
main a:hover,
footer a:hover,
.lang-switch:hover {
background: var(--color-text-primary);
color: var(--color-bg-primary);
border-bottom-color: transparent;
}
/* self-hosted video — custom terminal-style player (no native controls, so
only our own cursor shows; same-origin keeps the pixel cursor working) */
.player {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
font-family: var(--font-family-mono);
overflow: hidden;
}
.player video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; }
.player__big {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
color: #fff;
background: none;
border: 0;
}
.player.is-playing .player__big { display: none; }
.player__bar {
position: absolute;
left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
gap: 1ch;
padding: 0.5rem 1ch;
color: #fff;
font-size: var(--font-size-small);
background: linear-gradient(transparent, rgba(0, 0, 0, 0.55));
opacity: 0;
transition: opacity 0.2s ease;
}
.player:hover .player__bar,
.player:not(.is-playing) .player__bar { opacity: 1; }
.player__bar button { font: inherit; color: #fff; background: none; border: 0; padding: 0 0.4ch; line-height: 1; }
.player__bar button:hover { background: #fff; color: #000; }
.player__scrub { flex: 1; height: 3px; background: rgba(255, 255, 255, 0.3); position: relative; }
.player__fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: #fff; }
.player__time { white-space: nowrap; font-variant-numeric: tabular-nums; color: #fff; }
.player:fullscreen { width: 100vw; height: 100vh; aspect-ratio: auto; }
/* all images always in colour (selection drives what's shown, not hover) */
img { filter: none; }
/* make the [hidden] attribute win over .tui__preview img { display:block } */
[hidden] { display: none !important; }
.tui__status {
font-family: var(--font-family-mono);
font-size: var(--font-size-small);
color: var(--color-text-muted);
margin-top: 0.4rem;
}
/* frozen/locked preview indicator */
.filebrowser.is-frozen .tui__status { color: var(--color-text-primary); }
.filebrowser.is-frozen .tui__status::after { content: " ● locked"; }
/* hide every scrollbar site-wide — content still scrolls (wheel/touch/keys),
just no native chrome. keeps the clean terminal surface everywhere. */
* {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* old Edge/IE */
}
*::-webkit-scrollbar { /* WebKit/Blink */
display: none;
}
/* ─────────────────── LANGUAGE SWITCH ─────────────────── */
/* top-right toggle on the shell row: shows only the *other* language,
click to switch. you never see both at once. */
.topbar { position: relative; }
.lang-switch {
position: absolute;
top: 0;
right: 0;
margin-left: 0;
font-family: var(--font-family-mono);
color: var(--color-text-muted);
border-bottom: 1px dotted;
z-index: 2;
}
/* hover state comes from the shared "main a:hover, footer a:hover,
.lang-switch:hover" rule above (invert to black bg + white text) — a
second, later rule here used to override just the text color back to
dark, leaving black-on-black on hover. */
/* no static width reservation needed here anymore — syncLangSwitch() (shell.js)
already hides the toggle the moment a long line would reach it */