/* ========================================================================== Dark, minimal photography theme — shared by public site and admin. ========================================================================== */ :root { --bg: #0b0b0c; --bg-raise: #151517; --fg: #f4f4f2; --muted: #9a9a96; --line: #29292c; --danger: #e05b4d; --ok: #6fbf8f; --nav-h: 64px; } * { box-sizing: border-box; margin: 0; padding: 0; } html { scroll-behavior: smooth; } body { background: var(--bg); color: var(--fg); font-family: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif; font-size: 16px; line-height: 1.6; -webkit-font-smoothing: antialiased; } img { max-width: 100%; display: block; } a { color: inherit; text-decoration: none; } /* -------------------------------------------------------------------------- Public navigation — fixed, hides on scroll-down, returns on scroll-up -------------------------------------------------------------------------- */ .nav { position: fixed; inset: 0 0 auto 0; height: var(--nav-h); display: flex; align-items: center; justify-content: space-between; padding: 0 clamp(1.25rem, 4vw, 3rem); z-index: 100; background: linear-gradient(to bottom, rgba(0,0,0,.55), rgba(0,0,0,0)); transition: transform .35s ease, opacity .35s ease; } .nav.nav-hidden { transform: translateY(-100%); opacity: 0; pointer-events: none; } .nav-brand { font-size: 1.05rem; letter-spacing: .18em; text-transform: uppercase; font-weight: 500; } /* The public brand is the artist's name (user content): show it exactly as typed rather than force-uppercased, so characters like ß are not expanded to SS by the browser. Admin/login brand keeps the uppercase styling. */ .nav .nav-brand { text-transform: none; letter-spacing: .12em; } .nav-links { display: flex; gap: 2rem; } .nav-links a { font-size: .8rem; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); transition: color .2s; } .nav-links a:hover, .nav-links a.active { color: var(--fg); } /* -------------------------------------------------------------------------- Landing page -------------------------------------------------------------------------- */ .hero { position: relative; height: 100svh; overflow: hidden; } .hero img { width: 100%; height: 100%; object-fit: cover; } .hero-placeholder { width: 100%; height: 100%; background: radial-gradient(120% 90% at 50% 20%, #1d1d21 0%, #0b0b0c 70%); } .hero-caption { position: absolute; inset: auto 0 0 0; padding: clamp(2rem, 8vh, 6rem) clamp(1.25rem, 4vw, 3rem); background: linear-gradient(to top, rgba(0,0,0,.65), rgba(0,0,0,0)); } .hero-caption h1 { font-size: clamp(2.2rem, 6vw, 4.5rem); font-weight: 300; letter-spacing: .12em; /* Artist name shown as typed (see .nav .nav-brand) so ß stays ß. */ } .hero-scroll-hint { position: absolute; bottom: 1.2rem; left: 50%; translate: -50% 0; color: rgba(255,255,255,.7); font-size: 1.4rem; animation: hint 2.2s ease-in-out infinite; } @keyframes hint { 0%, 100% { transform: translateY(0); opacity: .5; } 50% { transform: translateY(8px); opacity: 1; } } .intro { max-width: 42rem; margin: 0 auto; padding: clamp(4rem, 14vh, 9rem) 1.5rem; text-align: center; } .intro p { font-size: clamp(1.05rem, 1.6vw, 1.3rem); font-weight: 300; color: var(--fg); white-space: pre-line; line-height: 1.9; } .intro .cta { display: inline-block; margin-top: 3rem; padding: .9rem 2.6rem; border: 1px solid var(--fg); font-size: .8rem; letter-spacing: .25em; text-transform: uppercase; transition: background .25s, color .25s; } .intro .cta:hover { background: var(--fg); color: var(--bg); } /* -------------------------------------------------------------------------- Contact page — landing-style centered layout -------------------------------------------------------------------------- */ .contact { min-height: 100svh; display: flex; flex-direction: column; justify-content: center; } .contact-title { font-size: clamp(1.8rem, 5vw, 3.2rem); font-weight: 300; letter-spacing: .1em; margin-bottom: 1.4rem; } .contact-methods { margin-top: 2.6rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; } .contact-methods a { font-size: 1.05rem; letter-spacing: .05em; color: var(--fg); border-bottom: 1px solid var(--line); padding-bottom: .2rem; transition: border-color .25s; } .contact-methods a:hover { border-color: var(--fg); } /* -------------------------------------------------------------------------- Prose — rendered Markdown for the legal pages (Impressum / Datenschutz) -------------------------------------------------------------------------- */ .prose { max-width: 46rem; font-weight: 300; } .prose h1, .prose h2, .prose h3 { font-weight: 400; letter-spacing: .04em; margin: 2rem 0 .8rem; } .prose h1 { font-size: 1.5rem; } .prose h2 { font-size: 1.2rem; } .prose h3 { font-size: 1.05rem; } .prose p { margin-bottom: 1rem; } .prose ul { margin: 0 0 1rem 1.3rem; } .prose li { margin-bottom: .35rem; } .prose a { text-decoration: underline; } .prose a:hover { color: var(--muted); } .prose-empty { color: var(--muted); } /* -------------------------------------------------------------------------- Showreel — one image per viewport, scroll snap -------------------------------------------------------------------------- */ body.reel-page { scroll-snap-type: y mandatory; } .reel-slide { height: 100svh; scroll-snap-align: start; scroll-snap-stop: always; display: flex; align-items: center; justify-content: center; background: #000; } .reel-slide img { max-width: 100%; max-height: 100svh; width: auto; height: auto; object-fit: contain; } .reel-empty { height: 100svh; display: grid; place-items: center; color: var(--muted); letter-spacing: .1em; } /* -------------------------------------------------------------------------- Gallery — password gate, thumbnail grid, lightbox -------------------------------------------------------------------------- */ .page { max-width: 1400px; margin: 0 auto; padding: calc(var(--nav-h) + 3rem) clamp(1rem, 3vw, 2.5rem) 4rem; } .page-title { font-weight: 300; letter-spacing: .1em; /* Gallery title is user content — shown as typed so ß stays ß. */ font-size: clamp(1.4rem, 3vw, 2.2rem); margin-bottom: .4rem; } .page-sub { color: var(--muted); font-size: .9rem; margin-bottom: 2.5rem; } .gate { min-height: 100svh; display: grid; place-items: center; padding: 1.5rem; } .gate-card { width: 100%; max-width: 24rem; text-align: center; } .gate-card h1 { font-weight: 300; letter-spacing: .12em; /* Gallery title is user content — shown as typed so ß stays ß. */ font-size: 1.4rem; margin-bottom: 1.8rem; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 6px; } /* A collapsed topic. Needed explicitly: the rule above is a class selector and would otherwise win over the browser's own [hidden] rule, leaving a collapsed section fully visible. */ .grid[hidden] { display: none; } .grid a { position: relative; aspect-ratio: 3 / 2; overflow: hidden; background: var(--bg-raise); } .grid img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s ease, opacity .4s; opacity: .92; } .grid a:hover img { transform: scale(1.03); opacity: 1; } /* Topic sections on the client gallery. Images without a topic render as a bare .grid above everything else, exactly as a gallery did before topics existed — no heading, nothing to collapse. Only actual topics get the heading below. */ .topic-head { margin: 2.6rem 0 .9rem; font-weight: 400; } .grid + .topic-head { margin-top: 3.2rem; } .topic-toggle { /* Reset the global button styling: this is a heading you can click, not a control that should look like one. */ display: flex; align-items: baseline; gap: .6rem; width: auto; margin: 0; padding: 0; background: none; border: 0; color: var(--fg); font: inherit; font-size: 1.05rem; letter-spacing: .04em; text-transform: none; cursor: pointer; text-align: left; } .topic-toggle:hover { color: var(--fg); opacity: .8; } .topic-chevron { display: inline-block; color: var(--muted); font-size: 1.2rem; line-height: 1; transition: transform .2s ease; } .topic-toggle[aria-expanded="true"] .topic-chevron { transform: rotate(90deg); } .topic-count { color: var(--muted); font-size: .78rem; letter-spacing: .1em; } /* Lightbox */ .lightbox { position: fixed; inset: 0; z-index: 200; background: rgba(0,0,0,.96); display: none; align-items: center; justify-content: center; } .lightbox.open { display: flex; } .lightbox img { max-width: 96vw; max-height: 96svh; object-fit: contain; } .lightbox button { position: absolute; background: none; border: none; color: rgba(255,255,255,.75); font-size: 2rem; cursor: pointer; padding: 1rem; line-height: 1; transition: color .2s; } .lightbox button:hover { color: #fff; } .lb-close { top: .5rem; right: .8rem; } .lb-prev { left: .4rem; top: 50%; translate: 0 -50%; } .lb-next { right: .4rem; top: 50%; translate: 0 -50%; } .lb-count { position: absolute; bottom: 1rem; left: 50%; translate: -50% 0; color: var(--muted); font-size: .8rem; letter-spacing: .15em; } /* -------------------------------------------------------------------------- Footer -------------------------------------------------------------------------- */ .footer { padding: 2.5rem clamp(1.25rem, 4vw, 3rem); color: var(--muted); font-size: .75rem; letter-spacing: .15em; /* Contains the artist name — shown as typed so ß stays ß. */ text-align: center; } .footer-links { display: flex; justify-content: center; gap: 1.6rem; margin-bottom: 1rem; } .footer-links a { text-transform: uppercase; color: var(--muted); transition: color .2s; } .footer-links a:hover { color: var(--fg); } /* -------------------------------------------------------------------------- Forms (shared) -------------------------------------------------------------------------- */ label { display: block; font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin: 1.1rem 0 .35rem; } input[type=text], input[type=password], input[type=date], input[type=number], textarea, select { width: 100%; padding: .7rem .9rem; background: var(--bg-raise); border: 1px solid var(--line); color: var(--fg); font: inherit; border-radius: 4px; } input:focus, textarea:focus { outline: 1px solid var(--muted); } /* Resolution picker: preset select, plus a custom px box when "Custom…" is chosen. The box is [hidden] otherwise, so the gap collapses on its own. */ .res-field { display: flex; gap: .6rem; } .res-field select { flex: 1; } .res-field input[type=number] { flex: 0 0 9rem; } textarea { min-height: 9rem; resize: vertical; } button, .btn { display: inline-block; margin-top: 1.4rem; padding: .75rem 1.8rem; background: var(--fg); color: var(--bg); border: none; font: inherit; font-size: .8rem; letter-spacing: .18em; text-transform: uppercase; cursor: pointer; border-radius: 4px; } .btn-ghost { background: none; color: var(--fg); border: 1px solid var(--line); } .btn-danger { background: var(--danger); color: #fff; } /* A download offered but not currently available: the gallery's archive is being rebuilt. Rendered as a with a title, so hovering explains why. */ .btn-disabled { opacity: .45; cursor: not-allowed; } /* Gallery heading row: title and subtitle on the left, download on the right. The subtitle carries the block's bottom margin, so the button sits flush with the title rather than adding space of its own. */ .page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; } .page-action { margin-top: 0; flex-shrink: 0; } @media (max-width: 600px) { /* Too narrow to sit beside the title; give it the full width instead. */ .page-head { gap: 0; } .page-head .page-sub { margin-bottom: 1.2rem; } .page-action { width: 100%; text-align: center; margin-bottom: 2.5rem; } } /* Archive build progress in the backoffice. */ .archive-bar { height: 4px; background: var(--line); border-radius: 2px; overflow: hidden; margin-bottom: 1rem; } .archive-bar span { display: block; height: 100%; width: 0; background: var(--fg); transition: width .3s ease; } .flash { padding: .8rem 1.1rem; border-radius: 4px; margin-bottom: 1.5rem; font-size: .9rem; } .flash-ok { background: rgba(111,191,143,.12); border: 1px solid rgba(111,191,143,.4); color: var(--ok); } .flash-error { background: rgba(224,91,77,.12); border: 1px solid rgba(224,91,77,.4); color: var(--danger); } /* -------------------------------------------------------------------------- Admin -------------------------------------------------------------------------- */ body.admin { background: #101012; } .admin-nav { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .8rem; padding: 1rem clamp(1rem, 3vw, 2rem); border-bottom: 1px solid var(--line); background: var(--bg); } .admin-nav .nav-links { flex-wrap: wrap; gap: 1.2rem; } .admin-main { max-width: 1000px; margin: 0 auto; padding: 2.5rem clamp(1rem, 3vw, 2rem) 5rem; } .admin-main h1 { font-weight: 400; font-size: 1.5rem; margin-bottom: 1.6rem; } .admin-main h2 { font-weight: 400; font-size: 1.1rem; margin: 2.2rem 0 1rem; } .card { background: var(--bg-raise); border: 1px solid var(--line); border-radius: 6px; padding: 1.5rem; margin-bottom: 1.5rem; } table { width: 100%; border-collapse: collapse; font-size: .92rem; } th, td { text-align: left; padding: .65rem .5rem; border-bottom: 1px solid var(--line); vertical-align: middle; } th { color: var(--muted); font-weight: 400; font-size: .75rem; letter-spacing: .12em; text-transform: uppercase; } .tag { display: inline-block; padding: .1rem .55rem; border-radius: 99px; font-size: .72rem; border: 1px solid var(--line); color: var(--muted); } .tag-lock { border-color: rgba(111,191,143,.5); color: var(--ok); } .tag-expired{ border-color: rgba(224,91,77,.5); color: var(--danger); } .thumb-row { display: flex; flex-wrap: wrap; gap: 10px; } .thumb-row figure { position: relative; width: 140px; } .thumb-row img { width: 140px; height: 100px; object-fit: cover; border-radius: 4px; background: #000; } .thumb-row figcaption { font-size: .7rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: .25rem; } /* Topic sections in the editor. Unlike the client gallery, the untopiced section is labelled here: it has to be a visible drop target, and so do topics holding nothing yet. A gallery with no topics at all renders no headings, so it looks untouched. */ .topic-block { margin-bottom: 1.6rem; } .topic-block-head { display: flex; align-items: baseline; gap: .6rem; font-weight: 400; font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); padding-bottom: .4rem; margin-bottom: .7rem; border-bottom: 1px solid var(--line); } .topic-block .thumb-row { min-height: 40px; } /* The drop target is the whole section, so an empty one is still hittable. */ .topic-block.drag-over { outline: 1px dashed var(--fg); outline-offset: 6px; border-radius: 4px; } .thumb-row figure.is-dragging { opacity: .4; } .thumb-row figure[draggable] { cursor: grab; } .topic-status { color: var(--danger); margin-bottom: .8rem; } /* Per-image menu: assign to a topic, or delete. */ .thumb-menu-btn { position: absolute; top: 4px; right: 4px; margin: 0; padding: .15rem .5rem; font-size: .7rem; line-height: 1.2; background: rgba(0,0,0,.65); color: #fff; border-radius: 3px; letter-spacing: 0; } .thumb-menu { position: absolute; top: 26px; right: 4px; z-index: 20; min-width: 150px; padding: .35rem 0; background: var(--bg-raise); border: 1px solid var(--line); border-radius: 4px; box-shadow: 0 8px 24px rgba(0,0,0,.5); } .thumb-menu-label { padding: .25rem .7rem .35rem; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); } .thumb-menu .topic-pick, .thumb-menu-danger { display: block; width: 100%; margin: 0; padding: .35rem .7rem; background: none; color: var(--fg); font-size: .78rem; letter-spacing: 0; text-transform: none; text-align: left; border-radius: 0; } .thumb-menu .topic-pick:hover, .thumb-menu-danger:hover { background: rgba(255,255,255,.07); } /* The topic this image is already in; shown, but not worth clicking. */ .thumb-menu .topic-pick.is-current { color: var(--muted); } .thumb-menu .topic-pick.is-current::after { content: " ✓"; } .thumb-menu form { margin-top: .35rem; border-top: 1px solid var(--line); padding-top: .35rem; } .thumb-menu-danger { color: var(--danger); } /* Upload queue */ .dropzone { border: 2px dashed var(--line); border-radius: 6px; padding: 2.5rem 1.5rem; text-align: center; color: var(--muted); cursor: pointer; transition: border-color .2s, color .2s; } .dropzone.drag { border-color: var(--fg); color: var(--fg); } .upload-list { margin-top: 1rem; font-size: .85rem; } .upload-item { display: flex; align-items: center; gap: .8rem; padding: .4rem 0; border-bottom: 1px solid var(--line); } .upload-item .name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .upload-item .state { color: var(--muted); min-width: 90px; text-align: right; } .upload-item .state.done { color: var(--ok); } .upload-item .state.error { color: var(--danger); } .upload-item .bar { width: 120px; height: 4px; background: var(--line); border-radius: 2px; overflow: hidden; } .upload-item .bar i { display: block; height: 100%; width: 0; background: var(--fg); transition: width .2s; } .help { color: var(--muted); font-size: .85rem; margin-top: .5rem; } .login-wrap { min-height: 80svh; display: grid; place-items: center; padding: 1.5rem; } .login-card { width: 100%; max-width: 22rem; } .login-card h1 { text-align: center; font-weight: 300; letter-spacing: .15em; text-transform: uppercase; font-size: 1.2rem; }