Compare commits
32 Commits
f848aac05d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 247ca6ac9a | |||
| 55d37ecd92 | |||
| 666681be7c | |||
| 11660ac63d | |||
| 46aff56577 | |||
| dd915e1797 | |||
| b665293922 | |||
| aa33ce56bf | |||
| ffe9fa11fc | |||
| 6b59b58082 | |||
| 892dfe600d | |||
| 4aae68d836 | |||
| 7f6bedebdc | |||
| 55926a84f2 | |||
| 932d7a7653 | |||
| 84ac7ae562 | |||
| 37fd39f488 | |||
| 0469e9dab5 | |||
| 113a954b16 | |||
| 5c5b267a7f | |||
| f318ef9a2f | |||
| 68474c5940 | |||
| fa7232cd1e | |||
| 37600ccb43 | |||
| a49603890b | |||
| d436474719 | |||
| 3c7cc6ae6b | |||
| d3f63251eb | |||
| 85148623d7 | |||
| 8f37bba08b | |||
| 62ec86dab7 | |||
| f8a5b0900b |
@@ -6,6 +6,31 @@
|
|||||||
remove this file → original shibui look returns.
|
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 */
|
/* self-hosted serif, used only for the project-info side panel */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Libre Baskerville";
|
font-family: "Libre Baskerville";
|
||||||
@@ -84,7 +109,10 @@
|
|||||||
|
|
||||||
/* always-open file menu (the shell's "ls") — only shown on the home page;
|
/* 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 */
|
on subpages it stays in the DOM (hidden) so typed commands still resolve */
|
||||||
.menu { margin: var(--spacing-sm) 0 0; }
|
/* 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; }
|
body:not(.home) .menu { display: none; }
|
||||||
|
|
||||||
/* easter-egg output line — takes no space until it actually has output */
|
/* easter-egg output line — takes no space until it actually has output */
|
||||||
@@ -119,8 +147,13 @@ body:not(.home) .menu { display: none; }
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
/* name + tags share the grid's last column but need their own row to put
|
||||||
|
the tag flush right without wrapping onto a second line — two grid items
|
||||||
|
left in the same cell just stack instead of sitting side by side */
|
||||||
|
.tui__row3 { display: flex; justify-content: space-between; align-items: baseline; gap: 1ch; min-width: 0; }
|
||||||
.tui__perm,
|
.tui__perm,
|
||||||
.tui__date { color: var(--color-text-muted); }
|
.tui__date,
|
||||||
|
.tui__tags { color: var(--color-text-muted); }
|
||||||
.tui__ls a:hover,
|
.tui__ls a:hover,
|
||||||
.tui__ls a.is-selected {
|
.tui__ls a.is-selected {
|
||||||
background: var(--color-text-primary);
|
background: var(--color-text-primary);
|
||||||
@@ -129,9 +162,11 @@ body:not(.home) .menu { display: none; }
|
|||||||
.tui__ls a:hover .tui__perm,
|
.tui__ls a:hover .tui__perm,
|
||||||
.tui__ls a:hover .tui__date,
|
.tui__ls a:hover .tui__date,
|
||||||
.tui__ls a:hover .tui__name,
|
.tui__ls a:hover .tui__name,
|
||||||
|
.tui__ls a:hover .tui__tags,
|
||||||
.tui__ls a.is-selected .tui__perm,
|
.tui__ls a.is-selected .tui__perm,
|
||||||
.tui__ls a.is-selected .tui__date,
|
.tui__ls a.is-selected .tui__date,
|
||||||
.tui__ls a.is-selected .tui__name { color: var(--color-bg-primary); }
|
.tui__ls a.is-selected .tui__name,
|
||||||
|
.tui__ls a.is-selected .tui__tags { color: var(--color-bg-primary); }
|
||||||
|
|
||||||
/* preview pane (ranger-style) below the listing */
|
/* preview pane (ranger-style) below the listing */
|
||||||
.tui__preview { margin: 1.25rem 0 0; }
|
.tui__preview { margin: 1.25rem 0 0; }
|
||||||
@@ -144,6 +179,10 @@ body:not(.home) .menu { display: none; }
|
|||||||
display: block;
|
display: block;
|
||||||
transition: opacity 0.25s ease;
|
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) */
|
/* project page: image files (no date column), show full image (contain) */
|
||||||
.tui--files .tui__ls a { grid-template-columns: 11ch 1fr; }
|
.tui--files .tui__ls a { grid-template-columns: 11ch 1fr; }
|
||||||
@@ -181,13 +220,10 @@ body:not(.home) .menu { display: none; }
|
|||||||
font-family: var(--font-family-serif);
|
font-family: var(--font-family-serif);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
text-align: left;
|
text-align: justify;
|
||||||
/* ragged-right with hyphenation reads better than a raw ragged edge at this
|
/* justified needs hyphenation, otherwise a narrow column like this one gets
|
||||||
column width; :lang() on <html> (set per-page by Hugo) picks the right
|
ugly gaps between words; :lang() on <html> (set per-page by Hugo) picks
|
||||||
hyphenation dictionary for en vs. de automatically. text-wrap:pretty asks
|
the right hyphenation dictionary for en vs. de automatically. */
|
||||||
the browser to look ahead across the whole paragraph (not just line by
|
|
||||||
line) so the rag varies naturally instead of coming out too even or
|
|
||||||
leaving a short orphan on the last line. */
|
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
-webkit-hyphens: auto;
|
-webkit-hyphens: auto;
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
@@ -222,6 +258,43 @@ body:not(.home) .menu { display: none; }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* small peek column — mirrors .proj-info's spot and width on the OTHER side
|
||||||
|
of the file list, showing whichever image is under the mouse while
|
||||||
|
something is locked. sits inside the space .proj-layout already reserves
|
||||||
|
to keep the file list anchored (see above), so it needs that reserved
|
||||||
|
padding to be at least 18rem + the gap wide to have room; 1300px keeps a
|
||||||
|
safety margin above the ~1168px point where that stops being true. plain
|
||||||
|
absolute positioning against .filebrowser — no scroll-tracking needed,
|
||||||
|
it just scrolls together with the file list it's attached to. */
|
||||||
|
.proj-peek { display: none; }
|
||||||
|
@media (min-width: 1300px) {
|
||||||
|
.filebrowser { position: relative; }
|
||||||
|
.proj-peek {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 3.6rem; /* same offset as .proj-info, clearing the shell lines */
|
||||||
|
right: 100%;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
width: 18rem;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.proj-peek.is-visible { opacity: 1; visibility: visible; }
|
||||||
|
/* same fixed shape as the portfolio-index preview (.tui__preview img above)
|
||||||
|
— a plan and a photo have wildly different natural ratios, so without
|
||||||
|
this the peek box would jump size on every hover */
|
||||||
|
.proj-peek img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 3 / 2;
|
||||||
|
object-fit: cover;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* prev/next as shell "cd" commands — same vocabulary as the rest of the site */
|
/* prev/next as shell "cd" commands — same vocabulary as the rest of the site */
|
||||||
.proj-nav {
|
.proj-nav {
|
||||||
font-family: var(--font-family-mono);
|
font-family: var(--font-family-mono);
|
||||||
@@ -251,48 +324,18 @@ body:not(.home) .menu { display: none; }
|
|||||||
}
|
}
|
||||||
.proj-path__seg--last { font-weight: 700; }
|
.proj-path__seg--last { font-weight: 700; }
|
||||||
|
|
||||||
/* secret `doom` — a small window floating over the site, sized to the 4:3 game */
|
/* secret retro games (doom, simcity, pacman, tetris) — each runs inline as
|
||||||
.doom-window {
|
<name>.sh inside a fake .<name> folder, same file-listing chrome as any
|
||||||
position: fixed;
|
other page. sized to the classic 4:3 DOS/arcade aspect ratio. */
|
||||||
top: 50%;
|
.game-player {
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: min(92vw, 640px);
|
|
||||||
z-index: 9999;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: #000;
|
|
||||||
border: 1px solid var(--color-text-primary);
|
|
||||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
.doom-window__bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.25rem 0.25rem 0.25rem 0.6rem;
|
|
||||||
font-family: var(--font-family-mono);
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
color: #fff;
|
|
||||||
background: #000;
|
|
||||||
cursor: move;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.doom-window__close {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #fff;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font: inherit;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0 0.6ch;
|
|
||||||
}
|
|
||||||
.doom-window__close:hover { color: #000; background: #fff; }
|
|
||||||
.doom-window iframe {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 4 / 3;
|
aspect-ratio: 4 / 3;
|
||||||
height: auto;
|
|
||||||
border: 0;
|
|
||||||
background: #000;
|
background: #000;
|
||||||
|
}
|
||||||
|
.game-player iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,9 +476,45 @@ footer a:hover,
|
|||||||
.player:not(.is-playing) .player__bar { opacity: 1; }
|
.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 { font: inherit; color: #fff; background: none; border: 0; padding: 0 0.4ch; line-height: 1; }
|
||||||
.player__bar button:hover { background: #fff; color: #000; }
|
.player__bar button:hover { background: #fff; color: #000; }
|
||||||
.player__scrub { flex: 1; height: 3px; background: rgba(255, 255, 255, 0.3); position: relative; }
|
/* fixed width + tightened tracking so ▶ and ❚❚ render as the same size
|
||||||
|
button instead of the pause glyph's two characters (with the font's
|
||||||
|
normal letter-spacing between them) widening it */
|
||||||
|
/* pause icon is two CSS bars, not a "❚❚" glyph — a monospace font's own
|
||||||
|
per-character cell width left a gap between the glyphs that no amount of
|
||||||
|
letter-spacing closed without also looking wrong at other sizes */
|
||||||
|
.player__play { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 2.4ch; }
|
||||||
|
.player.is-playing .player__playicon { visibility: hidden; }
|
||||||
|
.player__play::before,
|
||||||
|
.player__play::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: 2px;
|
||||||
|
height: 0.8em;
|
||||||
|
background: currentColor;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.player.is-playing .player__play::before { opacity: 1; left: calc(50% - 3px); }
|
||||||
|
.player.is-playing .player__play::after { opacity: 1; left: calc(50% + 1px); }
|
||||||
|
.player__scrub { flex: 1; height: 3px; background: rgba(255, 255, 255, 0.3); position: relative; cursor: pointer; }
|
||||||
.player__fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: #fff; }
|
.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__time { white-space: nowrap; font-variant-numeric: tabular-nums; color: #fff; }
|
||||||
|
.player__vol { width: 4ch; height: 3px; background: rgba(255, 255, 255, 0.3); position: relative; cursor: pointer; }
|
||||||
|
.player__volfill { position: absolute; left: 0; top: 0; bottom: 0; width: 100%; background: #fff; }
|
||||||
|
/* invisible hit-area extending past the thin visual bar — the bar itself
|
||||||
|
stays 3px, but clicking/dragging within 5px above or below it still
|
||||||
|
counts (rects used for the seek/volume math are still the element's own,
|
||||||
|
unaffected by this) */
|
||||||
|
.player__scrub::before,
|
||||||
|
.player__vol::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
.player:fullscreen { width: 100vw; height: 100vh; aspect-ratio: auto; }
|
.player:fullscreen { width: 100vw; height: 100vh; aspect-ratio: auto; }
|
||||||
|
|
||||||
/* all images always in colour (selection drives what's shown, not hover) */
|
/* all images always in colour (selection drives what's shown, not hover) */
|
||||||
@@ -477,6 +556,9 @@ img { filter: none; }
|
|||||||
border-bottom: 1px dotted;
|
border-bottom: 1px dotted;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.lang-switch:hover { color: var(--color-text-primary); border-bottom: 1px solid; }
|
/* 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)
|
/* no static width reservation needed here anymore — syncLangSwitch() (shell.js)
|
||||||
already hides the toggle the moment a long line would reach it */
|
already hides the toggle the moment a long line would reach it */
|
||||||
|
|||||||
@@ -44,8 +44,6 @@
|
|||||||
["ok", "Mounted /", 50],
|
["ok", "Mounted /", 50],
|
||||||
["ok", "Mounted /portfolio", 50],
|
["ok", "Mounted /portfolio", 50],
|
||||||
["ok", "Mounted /about", 50],
|
["ok", "Mounted /about", 50],
|
||||||
["ok", "Mounted /studio", 50],
|
|
||||||
["ok", "Mounted /openbureau", 50],
|
|
||||||
["ok", "Mounted /cv", 50],
|
["ok", "Mounted /cv", 50],
|
||||||
["ok", "Mounted /colophon", 110],
|
["ok", "Mounted /colophon", 110],
|
||||||
["st", "Prerendering portfolio...", 220],
|
["st", "Prerendering portfolio...", 220],
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
/* tui file browser — preview pane (image OR text panes), hover + freeze +
|
/* tui file browser — preview pane (image OR text panes), hover + lock +
|
||||||
keyboard. re-binds itself after pjax navigation (document listeners are added
|
keyboard. re-binds itself after pjax navigation (document listeners are added
|
||||||
once; rows/preview are re-queried per page). */
|
once; rows/preview are re-queried per page). */
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
var rows = [], img = null, texts = [], cap = null, cur = 0, frozen = false, container = null, player = null, pv = null;
|
var rows = [], img = null, texts = [], cap = null, cur = 0, locked = -1, container = null, player = null, pv = null;
|
||||||
|
|
||||||
function hideTexts() { texts.forEach(function (t) { t.hidden = true; }); }
|
function hideTexts() { texts.forEach(function (t) { t.hidden = true; }); }
|
||||||
function showText(k) { texts.forEach(function (t) { t.hidden = (t.getAttribute("data-text") !== k); }); }
|
function showText(k) { texts.forEach(function (t) { t.hidden = (t.getAttribute("data-text") !== k); }); }
|
||||||
function hidePlayer() { if (player) { player.hidden = true; if (pv && !pv.paused) pv.pause(); } }
|
function hidePlayer() { if (player) { player.hidden = true; if (pv && !pv.paused) pv.pause(); } }
|
||||||
function showPlayer() { if (player) player.hidden = false; }
|
function showPlayer() { if (player) player.hidden = false; }
|
||||||
function setFrozen(v) { frozen = v; if (container) container.classList.toggle("is-frozen", v); }
|
|
||||||
|
|
||||||
// tell the shell to "type" the matching command. navigable rows (those with an
|
// tell the shell to "type" the matching command. navigable rows (those with an
|
||||||
// href: ".." and the portfolio project folders) are directories → `cd`; the
|
// href: ".." and the portfolio project folders) are directories → `cd`; the
|
||||||
@@ -31,16 +30,16 @@
|
|||||||
if (img.complete) img.style.opacity = "1"; // already cached → no fade
|
if (img.complete) img.style.opacity = "1"; // already cached → no fade
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(i, silent, scroll) {
|
// renders row i into the MAIN preview pane. driven by lock (click) and
|
||||||
|
// keyboard nav only — hovering never touches this, it drives the small
|
||||||
|
// peek column instead (see bind()), so a locked file stays put while you
|
||||||
|
// browse the others.
|
||||||
|
function render(i, silent, scroll) {
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
if (i < 0) i = 0;
|
if (i < 0) i = 0;
|
||||||
if (i >= rows.length) i = rows.length - 1;
|
if (i >= rows.length) i = rows.length - 1;
|
||||||
cur = i;
|
cur = i;
|
||||||
for (var r = 0; r < rows.length; r++) rows[r].classList.remove("is-selected");
|
|
||||||
var a = rows[i];
|
var a = rows[i];
|
||||||
a.classList.add("is-selected");
|
|
||||||
// only scroll for keyboard nav — scrolling on hover nudges edge rows out from
|
|
||||||
// under the cursor, so the top/bottom row "sometimes" can't be clicked.
|
|
||||||
if (scroll && a.scrollIntoView) a.scrollIntoView({ block: "nearest" });
|
if (scroll && a.scrollIntoView) a.scrollIntoView({ block: "nearest" });
|
||||||
|
|
||||||
var src = a.getAttribute("data-img");
|
var src = a.getAttribute("data-img");
|
||||||
@@ -49,7 +48,8 @@
|
|||||||
if (src) { showImg(src); hideTexts(); hidePlayer(); }
|
if (src) { showImg(src); hideTexts(); hidePlayer(); }
|
||||||
else if (vid) { if (img) img.hidden = true; hideTexts(); showPlayer(); }
|
else if (vid) { if (img) img.hidden = true; hideTexts(); showPlayer(); }
|
||||||
else if (tkey) { if (img) img.hidden = true; hidePlayer(); showText(tkey); }
|
else if (tkey) { if (img) img.hidden = true; hidePlayer(); showText(tkey); }
|
||||||
else { if (img) img.hidden = true; hideTexts(); hidePlayer(); }
|
// rows with no preview of their own (".." above all) leave whatever was
|
||||||
|
// last shown in place instead of blanking the pane out
|
||||||
|
|
||||||
if (cap) {
|
if (cap) {
|
||||||
var n = a.querySelector(".tui__name");
|
var n = a.querySelector(".tui__name");
|
||||||
@@ -57,9 +57,17 @@
|
|||||||
cap.textContent = (n ? n.textContent.trim() : a.getAttribute("data-name")) + (y ? " " + y.textContent.trim() : "");
|
cap.textContent = (n ? n.textContent.trim() : a.getAttribute("data-name")) + (y ? " " + y.textContent.trim() : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent) ghostCmd(a); // echo into the shell, except on the initial load
|
if (!silent) ghostCmd(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lock(i) {
|
||||||
|
locked = i;
|
||||||
|
for (var r = 0; r < rows.length; r++) rows[r].classList.remove("is-selected");
|
||||||
|
if (rows[i]) rows[i].classList.add("is-selected");
|
||||||
|
if (container) container.classList.toggle("is-frozen", locked >= 0);
|
||||||
|
}
|
||||||
|
function unlock() { lock(-1); }
|
||||||
|
|
||||||
function bind() {
|
function bind() {
|
||||||
var list = document.querySelector(".filebrowser .tui__ls");
|
var list = document.querySelector(".filebrowser .tui__ls");
|
||||||
container = list ? list.closest(".filebrowser") : null;
|
container = list ? list.closest(".filebrowser") : null;
|
||||||
@@ -69,21 +77,73 @@
|
|||||||
player = document.querySelector(".tui__preview .player");
|
player = document.querySelector(".tui__preview .player");
|
||||||
pv = player ? player.querySelector("video") : null;
|
pv = player ? player.querySelector("video") : null;
|
||||||
cap = document.querySelector(".tui__status");
|
cap = document.querySelector(".tui__status");
|
||||||
cur = 0; frozen = false;
|
cur = 0; locked = -1;
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
|
|
||||||
|
// small side-by-side peek — sits to the left of the file list (mirroring
|
||||||
|
// the info text's spot on the right, same 18rem width) and shows
|
||||||
|
// whichever OTHER image is under the mouse while something is locked,
|
||||||
|
// without disturbing the main preview.
|
||||||
|
var peek = null, peekImg = null;
|
||||||
|
if (container) {
|
||||||
|
peek = document.createElement("div");
|
||||||
|
peek.className = "proj-peek";
|
||||||
|
peekImg = document.createElement("img");
|
||||||
|
peekImg.alt = "";
|
||||||
|
peek.appendChild(peekImg);
|
||||||
|
container.insertBefore(peek, container.firstChild);
|
||||||
|
}
|
||||||
|
function showPeek(a) {
|
||||||
|
var src = a.getAttribute("data-img");
|
||||||
|
if (!peek || !src) { if (peek) peek.classList.remove("is-visible"); return; }
|
||||||
|
peekImg.src = src;
|
||||||
|
peek.classList.add("is-visible");
|
||||||
|
}
|
||||||
|
function hidePeek() { if (peek) peek.classList.remove("is-visible"); }
|
||||||
|
|
||||||
rows.forEach(function (a, i) {
|
rows.forEach(function (a, i) {
|
||||||
a.addEventListener("mouseenter", function () { if (!frozen) select(i); });
|
// portfolio-index rows are real links (data-img + href together) — a
|
||||||
|
// single click there used to jump straight into the project, which
|
||||||
|
// meant hovering was the only way to preview a different one first.
|
||||||
|
// now a click just locks/unlocks it like any other row; the preview
|
||||||
|
// image is the dedicated way to actually navigate in.
|
||||||
|
var isProjectLink = a.hasAttribute("href") && a.hasAttribute("data-img");
|
||||||
|
a.addEventListener("mouseenter", function () {
|
||||||
|
// the peek column only exists for images — text/video rows (like on
|
||||||
|
// about) have no equivalent, so they always drive the main preview
|
||||||
|
// directly, lock or no lock, same as before locking existed
|
||||||
|
if (locked < 0 || !a.hasAttribute("data-img")) { hidePeek(); render(i); }
|
||||||
|
else if (i !== locked) showPeek(a);
|
||||||
|
else hidePeek();
|
||||||
|
});
|
||||||
a.addEventListener("click", function (e) {
|
a.addEventListener("click", function (e) {
|
||||||
if (a.getAttribute("href")) return; // navigation rows behave normally
|
if (isProjectLink) e.preventDefault(); // never navigate from the row itself
|
||||||
e.preventDefault();
|
else if (a.getAttribute("href")) return; // ".." navigates normally
|
||||||
if (frozen && cur === i) { setFrozen(false); ghostClear(); }
|
else e.preventDefault();
|
||||||
else { select(i); setFrozen(true); }
|
if (locked === i) { unlock(); ghostClear(); }
|
||||||
|
else { lock(i); render(i, true); }
|
||||||
|
hidePeek();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (list) list.addEventListener("mouseleave", hidePeek);
|
||||||
|
|
||||||
var start = rows.findIndex(function (a) { return a.classList.contains("is-selected"); });
|
var start = rows.findIndex(function (a) { return a.classList.contains("is-selected"); });
|
||||||
select(start < 0 ? 0 : start, true); // initial selection: don't echo into the shell yet
|
start = start < 0 ? 0 : start;
|
||||||
|
render(start, true); // initial preview: don't echo into the shell yet
|
||||||
|
// portfolio/about listings start locked on their first file — games stay
|
||||||
|
// unlocked, there's nothing else to browse there
|
||||||
|
if (container && !container.classList.contains("game-embed")) lock(start);
|
||||||
|
|
||||||
|
if (img) {
|
||||||
|
img.onclick = function () {
|
||||||
|
var a = rows[cur];
|
||||||
|
if (a && a.hasAttribute("href") && a.hasAttribute("data-img")) {
|
||||||
|
var href = a.getAttribute("href");
|
||||||
|
if (window.pjax) window.pjax(href); else window.location.href = href;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// warm the cache so switching between gallery images is instant
|
// warm the cache so switching between gallery images is instant
|
||||||
var srcs = rows.map(function (a) { return a.getAttribute("data-img"); }).filter(Boolean);
|
var srcs = rows.map(function (a) { return a.getAttribute("data-img"); }).filter(Boolean);
|
||||||
@@ -94,16 +154,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// one-time document listeners (reference the current bound state)
|
// one-time document listeners (reference the current bound state)
|
||||||
|
// clicking elsewhere IN THE WIDGET (the preview image, blank space in the
|
||||||
|
// list) releases the lock; clicks out in the text/page don't — a click to
|
||||||
|
// read or select a word shouldn't unlock the currently selected file.
|
||||||
document.addEventListener("click", function (e) {
|
document.addEventListener("click", function (e) {
|
||||||
if (e.target.closest && e.target.closest(".tui__ls a")) return;
|
if (e.target.closest && e.target.closest(".tui__ls a")) return;
|
||||||
if (frozen) { setFrozen(false); ghostClear(); }
|
if (!(e.target.closest && e.target.closest(".filebrowser"))) return;
|
||||||
|
if (locked >= 0) { unlock(); ghostClear(); }
|
||||||
});
|
});
|
||||||
document.addEventListener("keydown", function (e) {
|
document.addEventListener("keydown", function (e) {
|
||||||
var t = e.target;
|
var t = e.target;
|
||||||
if (t && (t.isContentEditable || /^(input|textarea|select)$/i.test(t.tagName))) return;
|
if (t && (t.isContentEditable || /^(input|textarea|select)$/i.test(t.tagName))) return;
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
if (e.key === "ArrowDown") { e.preventDefault(); select(cur + 1, false, true); }
|
if (e.key === "ArrowDown") { e.preventDefault(); render(cur + 1, false, true); }
|
||||||
else if (e.key === "ArrowUp") { e.preventDefault(); select(cur - 1, false, true); }
|
else if (e.key === "ArrowUp") { e.preventDefault(); render(cur - 1, false, true); }
|
||||||
else if (e.key === "Enter") {
|
else if (e.key === "Enter") {
|
||||||
var href = rows[cur] && rows[cur].getAttribute("href");
|
var href = rows[cur] && rows[cur].getAttribute("href");
|
||||||
if (href) { if (window.pjax) window.pjax(href); else window.location.href = href; }
|
if (href) { if (window.pjax) window.pjax(href); else window.location.href = href; }
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
var fill = root.querySelector(".player__fill");
|
var fill = root.querySelector(".player__fill");
|
||||||
var time = root.querySelector(".player__time");
|
var time = root.querySelector(".player__time");
|
||||||
var mute = root.querySelector(".player__mute");
|
var mute = root.querySelector(".player__mute");
|
||||||
|
var vol = root.querySelector(".player__vol");
|
||||||
|
var volFill = root.querySelector(".player__volfill");
|
||||||
var full = root.querySelector(".player__full");
|
var full = root.querySelector(".player__full");
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
|
|
||||||
@@ -23,19 +25,65 @@
|
|||||||
if (big) big.addEventListener("click", toggle);
|
if (big) big.addEventListener("click", toggle);
|
||||||
v.addEventListener("click", toggle);
|
v.addEventListener("click", toggle);
|
||||||
|
|
||||||
v.addEventListener("play", function () { root.classList.add("is-playing"); if (play) play.textContent = "❚❚"; });
|
// drive the scrub bar off rAF instead of `timeupdate` — timeupdate only
|
||||||
v.addEventListener("pause", function () { root.classList.remove("is-playing"); if (play) play.textContent = "▶"; });
|
// fires a handful of times a second (browser-dependent), which made the
|
||||||
v.addEventListener("loadedmetadata", function () { if (time) time.textContent = "00:00 / " + fmt(v.duration); });
|
// fill visibly step instead of glide; rAF ties it to the display's own
|
||||||
v.addEventListener("timeupdate", function () {
|
// refresh rate, only running while actually playing.
|
||||||
|
var raf = null;
|
||||||
|
function sync() {
|
||||||
var p = v.duration ? v.currentTime / v.duration : 0;
|
var p = v.duration ? v.currentTime / v.duration : 0;
|
||||||
if (fill) fill.style.width = (p * 100) + "%";
|
if (fill) fill.style.width = (p * 100) + "%";
|
||||||
if (time) time.textContent = fmt(v.currentTime) + " / " + fmt(v.duration || 0);
|
if (time) time.textContent = fmt(v.currentTime) + " / " + fmt(v.duration || 0);
|
||||||
|
}
|
||||||
|
function tick() { sync(); raf = requestAnimationFrame(tick); }
|
||||||
|
|
||||||
|
// the pause icon is drawn in CSS (two bars via ::before/::after on
|
||||||
|
// .is-playing), not a "❚❚" glyph — a monospace font's letter-spacing
|
||||||
|
// between two heavy-bar characters left a gap no amount of
|
||||||
|
// letter-spacing closed cleanly. the ▶ span just hides/shows.
|
||||||
|
v.addEventListener("play", function () {
|
||||||
|
root.classList.add("is-playing");
|
||||||
|
if (!raf) raf = requestAnimationFrame(tick);
|
||||||
});
|
});
|
||||||
|
v.addEventListener("pause", function () {
|
||||||
|
root.classList.remove("is-playing");
|
||||||
|
if (raf) { cancelAnimationFrame(raf); raf = null; }
|
||||||
|
sync(); // one last sync so the bar lands exactly on the paused frame
|
||||||
|
});
|
||||||
|
v.addEventListener("ended", function () { if (raf) { cancelAnimationFrame(raf); raf = null; } });
|
||||||
|
v.addEventListener("loadedmetadata", function () { if (time) time.textContent = "00:00 / " + fmt(v.duration); });
|
||||||
|
v.addEventListener("seeked", sync); // scrubbing while paused still needs to reflect immediately
|
||||||
if (scrub) scrub.addEventListener("click", function (e) {
|
if (scrub) scrub.addEventListener("click", function (e) {
|
||||||
var r = scrub.getBoundingClientRect();
|
var r = scrub.getBoundingClientRect();
|
||||||
if (v.duration) v.currentTime = ((e.clientX - r.left) / r.width) * v.duration;
|
if (v.duration) v.currentTime = ((e.clientX - r.left) / r.width) * v.duration;
|
||||||
});
|
});
|
||||||
if (mute) mute.addEventListener("click", function () { v.muted = !v.muted; mute.textContent = v.muted ? "muted" : "vol"; });
|
// volume level, not just mute — click or drag the little bar next to
|
||||||
|
// the mute button, same interaction as the scrub bar
|
||||||
|
function syncVol() {
|
||||||
|
var level = v.muted ? 0 : v.volume;
|
||||||
|
if (volFill) volFill.style.width = (level * 100) + "%";
|
||||||
|
if (mute) mute.textContent = (v.muted || v.volume === 0) ? "muted" : "vol";
|
||||||
|
}
|
||||||
|
function setVolFromEvent(e) {
|
||||||
|
var r = vol.getBoundingClientRect();
|
||||||
|
var level = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
|
||||||
|
v.volume = level;
|
||||||
|
v.muted = level <= 0;
|
||||||
|
}
|
||||||
|
if (vol) {
|
||||||
|
vol.addEventListener("click", setVolFromEvent);
|
||||||
|
vol.addEventListener("mousedown", function (e) {
|
||||||
|
setVolFromEvent(e);
|
||||||
|
function onMove(ev) { setVolFromEvent(ev); }
|
||||||
|
function onUp() { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); }
|
||||||
|
document.addEventListener("mousemove", onMove);
|
||||||
|
document.addEventListener("mouseup", onUp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
v.addEventListener("volumechange", syncVol);
|
||||||
|
syncVol();
|
||||||
|
|
||||||
|
if (mute) mute.addEventListener("click", function () { v.muted = !v.muted; syncVol(); });
|
||||||
if (full) full.addEventListener("click", function () {
|
if (full) full.addEventListener("click", function () {
|
||||||
if (document.fullscreenElement) document.exitFullscreen();
|
if (document.fullscreenElement) document.exitFullscreen();
|
||||||
else if (root.requestFullscreen) root.requestFullscreen();
|
else if (root.requestFullscreen) root.requestFullscreen();
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
var email = bar.getAttribute("data-email");
|
var email = bar.getAttribute("data-email");
|
||||||
if (info) lines.push(info);
|
if (info) lines.push(info);
|
||||||
if (email) lines.push(email);
|
if (email) lines.push(email);
|
||||||
lines.push("type: portfolio · about · studio · openbureau");
|
lines.push("type: portfolio · about");
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +151,6 @@
|
|||||||
l.push("├── portfolio/");
|
l.push("├── portfolio/");
|
||||||
work.forEach(function (s, i) { l.push("│ " + (i === work.length - 1 ? "└── " : "├── ") + s + "/"); });
|
work.forEach(function (s, i) { l.push("│ " + (i === work.length - 1 ? "└── " : "├── ") + s + "/"); });
|
||||||
l.push("├── about/");
|
l.push("├── about/");
|
||||||
l.push("├── studio/");
|
|
||||||
l.push("├── openbureau/");
|
|
||||||
l.push("├── .colophon");
|
l.push("├── .colophon");
|
||||||
l.push("└── .legal-notice");
|
l.push("└── .legal-notice");
|
||||||
return l.join("\n");
|
return l.join("\n");
|
||||||
@@ -168,7 +166,7 @@
|
|||||||
" |_|\\_\\ degree: ba architecture",
|
" |_|\\_\\ degree: ba architecture",
|
||||||
" status: ma in progress",
|
" status: ma in progress",
|
||||||
" uptime: " + up + "s",
|
" uptime: " + up + "s",
|
||||||
" ~: portfolio · about · studio · openbureau"
|
" ~: portfolio · about"
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,50 +203,69 @@
|
|||||||
setTimeout(function () { pre.remove(); }, 4200);
|
setTimeout(function () { pre.remove(); }, 4200);
|
||||||
}
|
}
|
||||||
|
|
||||||
// secret: `doom` boots shareware DOOM (id Software) in a draggable window
|
// secret: retro games (doom, simcity, pacman, tetris) cd into a fake
|
||||||
function doom() {
|
// ".<name>" folder and run "<name>.sh" — archive.org DOSBox emulators
|
||||||
if (document.querySelector(".doom-window")) return;
|
// embedded inline in the same file-listing chrome as any other page (not a
|
||||||
var win = document.createElement("div");
|
// floating window). ".." is a real link back to wherever you typed the
|
||||||
win.className = "doom-window";
|
// command from, so leaving is just normal navigation — nav.js's pjax swap
|
||||||
var bar = document.createElement("div");
|
// restores the real page (and real breadcrumb) on its own. archive.org
|
||||||
bar.className = "doom-window__bar";
|
// still gates each one behind its own "click to begin" — that's the
|
||||||
var label = document.createElement("span");
|
// emulator requiring a real user gesture before it can play audio, a
|
||||||
label.className = "doom-window__title";
|
// cross-origin iframe we can't click into or configure around.
|
||||||
label.textContent = "doom";
|
var GAMES = {
|
||||||
var close = document.createElement("button");
|
doom: "DoomsharewareEpisode",
|
||||||
close.className = "doom-window__close";
|
simcity: "msdos_SimCity_1989",
|
||||||
close.setAttribute("aria-label", "close");
|
pacman: "msdos_Pac-Man_1983",
|
||||||
close.textContent = "✕";
|
tetris: "msdos_Tetris_1987"
|
||||||
var frame = document.createElement("iframe");
|
};
|
||||||
frame.src = "https://archive.org/embed/DoomsharewareEpisode";
|
function launchGame(name) {
|
||||||
frame.setAttribute("allowfullscreen", "");
|
var archiveId = GAMES[name];
|
||||||
frame.allow = "fullscreen; autoplay; gamepad";
|
var main = document.getElementById("main-content");
|
||||||
bar.appendChild(label);
|
if (!archiveId || !main || document.querySelector(".game-embed")) return;
|
||||||
bar.appendChild(close);
|
var back = window.location.pathname + window.location.search;
|
||||||
win.appendChild(bar);
|
var file = name + ".sh";
|
||||||
win.appendChild(frame);
|
|
||||||
document.body.appendChild(win);
|
|
||||||
|
|
||||||
function kill() { win.remove(); document.removeEventListener("keydown", esc); }
|
var pathEl = document.querySelector(".prompt__path");
|
||||||
function esc(e) { if (e.key === "Escape") kill(); }
|
if (pathEl && /\$$/.test(pathEl.innerHTML)) {
|
||||||
close.addEventListener("click", kill);
|
pathEl.innerHTML = pathEl.innerHTML.replace(/\$$/, '/<span class="prompt__seg">.' + name + '</span>$');
|
||||||
document.addEventListener("keydown", esc);
|
}
|
||||||
|
|
||||||
// drag by the title bar (shield the iframe so it doesn't swallow the mouse)
|
main.innerHTML =
|
||||||
bar.addEventListener("mousedown", function (e) {
|
'<div class="tui tui--files filebrowser game-embed">' +
|
||||||
if (e.target === close) return;
|
'<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>' +
|
||||||
var r = win.getBoundingClientRect();
|
'<p class="tui__cmd tui__total">total 1</p>' +
|
||||||
win.style.left = r.left + "px";
|
'<ul class="tui__ls">' +
|
||||||
win.style.top = r.top + "px";
|
'<li><a href="' + back + '" data-name="..">' +
|
||||||
win.style.transform = "none";
|
'<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>' +
|
||||||
var dx = e.clientX - r.left, dy = e.clientY - r.top;
|
'</a></li>' +
|
||||||
frame.style.pointerEvents = "none";
|
'<li><a data-name="' + file + '" class="is-selected">' +
|
||||||
function mv(ev) { win.style.left = (ev.clientX - dx) + "px"; win.style.top = (ev.clientY - dy) + "px"; }
|
'<span class="tui__perm">-rwxr-xr-x</span><span class="tui__name">' + file + '</span>' +
|
||||||
function up() { frame.style.pointerEvents = ""; document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up); }
|
'</a></li>' +
|
||||||
document.addEventListener("mousemove", mv);
|
'</ul>' +
|
||||||
document.addEventListener("mouseup", up);
|
'<figure class="tui__preview">' +
|
||||||
e.preventDefault();
|
'<div class="game-player">' +
|
||||||
});
|
'<iframe src="https://archive.org/embed/' + archiveId + '" allowfullscreen allow="fullscreen; autoplay; gamepad"></iframe>' +
|
||||||
|
'</div>' +
|
||||||
|
'<figcaption class="tui__status">' + file + ' — running</figcaption>' +
|
||||||
|
'</figure>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
// re-run the same wiring a real pjax navigation would (file-row hover/
|
||||||
|
// click, route rebuilding) against this injected markup
|
||||||
|
document.dispatchEvent(new CustomEvent("pjax:load"));
|
||||||
|
|
||||||
|
// the home page's always-open menu lives in the header, not #main-content
|
||||||
|
// — hide it too, otherwise its own "$ ls" sits right above the game's and
|
||||||
|
// the listing looks doubled. restored on the next real navigation
|
||||||
|
// (leaving the game), which also fires pjax:load — registered after the
|
||||||
|
// dispatch above so it doesn't just catch its own event.
|
||||||
|
if (menu && !menu.hidden) {
|
||||||
|
menu.hidden = true;
|
||||||
|
document.addEventListener("pjax:load", function unhide() {
|
||||||
|
menu.hidden = false;
|
||||||
|
document.removeEventListener("pjax:load", unhide);
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function egg(lc, raw) {
|
function egg(lc, raw) {
|
||||||
@@ -259,7 +276,7 @@
|
|||||||
if (/^rm\b/.test(lc)) return "rm: some things you keep.";
|
if (/^rm\b/.test(lc)) return "rm: some things you keep.";
|
||||||
if (lc === "whoami") return "karim";
|
if (lc === "whoami") return "karim";
|
||||||
if (lc === "pwd") return pwd();
|
if (lc === "pwd") return pwd();
|
||||||
if (lc === "help" || /^man\b/.test(lc)) return "nav: portfolio · about · studio · openbureau · cv\nbasics: info · contact · date · time · tree · fetch · history\ntheme: dark · light";
|
if (lc === "help" || /^man\b/.test(lc)) return "nav: portfolio · about · cv\nbasics: info · contact · date · time · tree · fetch · history\ntheme: dark · light";
|
||||||
if (lc === "hello" || lc === "hi" || lc === "hey") return "hi :)";
|
if (lc === "hello" || lc === "hi" || lc === "hey") return "hi :)";
|
||||||
if (lc === "exit" || lc === "logout") return "there is no exit. only architecture.";
|
if (lc === "exit" || lc === "logout") return "there is no exit. only architecture.";
|
||||||
if (lc === "coffee" || lc === "brew") return "☕ brewing… (418: i'm a teapot)";
|
if (lc === "coffee" || lc === "brew") return "☕ brewing… (418: i'm a teapot)";
|
||||||
@@ -270,7 +287,7 @@
|
|||||||
// tab-completion over known commands + navigable routes
|
// tab-completion over known commands + navigable routes
|
||||||
var COMMANDS = ["help", "man", "info", "contact", "date", "time", "tree", "fetch", "neofetch",
|
var COMMANDS = ["help", "man", "info", "contact", "date", "time", "tree", "fetch", "neofetch",
|
||||||
"uname", "history", "clear", "echo", "pwd", "whoami", "ls", "cd", "cat", "open",
|
"uname", "history", "clear", "echo", "pwd", "whoami", "ls", "cd", "cat", "open",
|
||||||
"dark", "light", "theme", "sl", "doom", "coffee"];
|
"dark", "light", "theme", "sl", "doom", "simcity", "pacman", "tetris", "coffee"];
|
||||||
function complete() {
|
function complete() {
|
||||||
var val = input.textContent;
|
var val = input.textContent;
|
||||||
var parts = val.split(" ");
|
var parts = val.split(" ");
|
||||||
@@ -298,7 +315,8 @@
|
|||||||
remember(cmd);
|
remember(cmd);
|
||||||
|
|
||||||
if (lc === "clear") { say(""); input.textContent = ""; return; }
|
if (lc === "clear") { say(""); input.textContent = ""; return; }
|
||||||
if (lc === "doom") { doom(); say(""); input.textContent = ""; return; }
|
if (lc === "doom" || lc === "simcity" || lc === "tetris") { launchGame(lc); say(""); input.textContent = ""; return; }
|
||||||
|
if (lc === "pacman" || lc === "pac-man") { launchGame("pacman"); say(""); input.textContent = ""; return; }
|
||||||
if (lc === "sl") { sl(); say(""); input.textContent = ""; return; }
|
if (lc === "sl") { sl(); say(""); input.textContent = ""; return; }
|
||||||
if (lc === "dark" || lc === "light") { setTheme(lc); say(""); input.textContent = ""; return; }
|
if (lc === "dark" || lc === "light") { setTheme(lc); say(""); input.textContent = ""; return; }
|
||||||
if (lc === "theme") { var next = document.documentElement.classList.contains("dark") ? "light" : "dark"; setTheme(next); say("theme: " + next); input.textContent = ""; return; }
|
if (lc === "theme") { var next = document.documentElement.classList.contains("dark") ? "light" : "dark"; setTheme(next); say("theme: " + next); input.textContent = ""; return; }
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ description: "über karim gabriele varano"
|
|||||||
layout: "about"
|
layout: "about"
|
||||||
---
|
---
|
||||||
|
|
||||||
mein name ist karim gabriele varano. ich bin im rheintal im kanton st. gallen aufgewachsen. vor der architektur habe ich in der elektrotechnik gearbeitet, auf der baustelle und in der planung. ich bin durch die gestalterische vorbildung gegangen und von dort ins architekturstudium an der ost und der hslu. ich habe einen bachelor in architektur und arbeite zurzeit auf den master hin. was mich an der architektur fesselt, ist ihre tiefe. sie bewegt sich zwischen dem gewicht eines materials und dem gewicht einer gesellschaftlichen krise. sie borgt sich aus dem ingenieurwesen, der soziologie, der kunst, der politik. sie ist keine wissenschaft. sie ist der ort, an dem die wissenschaften sich treffen, und an dem dieses treffen in etwas realem und bewohnbarem enden muss.
|
mein name ist karim gabriele varano. ich bin im rheintal im kanton st. gallen aufgewachsen. bevor ich zur architektur kam, habe ich in der elektrotechnik gearbeitet, auf der baustelle und in der planung. nach der gestalterischen bms habe ich architektur studiert, an der ost und später an der hslu. heute habe ich einen bachelor in architektur und studiere im master.
|
||||||
|
|
||||||
|
an der architektur interessiert mich vor allem ihre vielschichtigkeit. sie bedient sich am ingenieurwesen, an der soziologie, an der kunst, an der politik. eine eigene wissenschaft ist sie nicht, sondern der ort, an dem sich diese bereiche treffen müssen. und wo am ende etwas gebautes, bewohnbares daraus werden muss.
|
||||||
|
|
||||||
ich bin offen für [zusammenarbeit](mailto:hi@karimgabrielevarano.xyz).
|
ich bin offen für [zusammenarbeit](mailto:hi@karimgabrielevarano.xyz).
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ diese seite ist ein kleines terminal.
|
|||||||
|
|
||||||
sie ist statisch. hugo verwandelt meinen text in einfache html-dateien, und mehr gibt der server nie heraus. keine datenbank, kein backend, nichts zum einloggen. sie läuft auf fast jedem host.
|
sie ist statisch. hugo verwandelt meinen text in einfache html-dateien, und mehr gibt der server nie heraus. keine datenbank, kein backend, nichts zum einloggen. sie läuft auf fast jedem host.
|
||||||
|
|
||||||
die leiste oben ist eine echte shell. du kannst darin tippen. portfolio, about, studio, openbureau bringen dich zu diesen seiten. help zeigt die grundlagen, tab vervollständigt ein wort, die pfeiltaste nach oben holt zurück, was du vorher getippt hast. nicht alles, was sie kennt, steht in der liste. jede seite ist auch eine dateiliste, du kannst die projekte und sogar das impressum wie verzeichnisse durchsehen und an ort und stelle lesen.
|
die leiste oben ist eine echte shell. du kannst darin tippen. portfolio, about bringen dich zu diesen seiten. help zeigt die grundlagen, tab vervollständigt ein wort, die pfeiltaste nach oben holt zurück, was du vorher getippt hast. nicht alles, was sie kennt, steht in der liste. jede seite ist auch eine dateiliste, du kannst die projekte und sogar das impressum wie verzeichnisse durchsehen und an ort und stelle lesen.
|
||||||
|
|
||||||
der rest sind kleinigkeiten. die schrift ist iosevka curly slab, hier gehostet und von niemandem nachgeladen. der cursor ist pixel für pixel gezeichnet. es gibt einen dunkelmodus, wenn du dark tippst. nichts wird verfolgt, keine cookies, kein tracking.
|
der rest sind kleinigkeiten. die schrift ist iosevka, hier gehostet und von niemandem nachgeladen. der cursor ist pixel für pixel gezeichnet. es gibt einen dunkelmodus, wenn du dark tippst. nichts wird verfolgt, keine cookies, kein tracking.
|
||||||
|
|
||||||
sie folgt derselben idee wie [openbureau](/de/openbureau/): besitze deine werkzeuge, halte deine formate lesbar. die inhalte stehen unter cc by-sa, hugo und das theme sind open source (das [impressum](/de/legal-notice/) hat die details), und die seite baut zu einem docker-image, sodass jeder seine eigene kopie betreiben kann.
|
sie folgt derselben idee wie [openbureau](/de/about/): besitze deine werkzeuge, halte deine formate lesbar. die inhalte stehen unter cc by-sa, hugo und das theme sind open source (das [impressum](/de/legal-notice/) hat die details), und die seite baut zu einem docker-image, sodass jeder seine eigene kopie betreiben kann.
|
||||||
|
|
||||||
geschrieben zwischen rheintal und luzern.
|
geschrieben zwischen rheintal und luzern.
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ this site is a small terminal.
|
|||||||
|
|
||||||
it's static. hugo turns my text into plain html files, and that's all the server ever hands out. no database, no backend, nothing to log into. it runs on almost any host.
|
it's static. hugo turns my text into plain html files, and that's all the server ever hands out. no database, no backend, nothing to log into. it runs on almost any host.
|
||||||
|
|
||||||
the bar at the top is a real shell. you can type in it. work, about, studio, openbureau take you to those pages. help shows the basics, tab completes a word, the up arrow brings back what you typed before. not everything it knows is in the list. every page is also a file listing, so you browse the projects, and even the legal notice, like directories and read them on the spot.
|
the bar at the top is a real shell. you can type in it. work, about take you to those pages. help shows the basics, tab completes a word, the up arrow brings back what you typed before. not everything it knows is in the list. every page is also a file listing, so you browse the projects, and even the legal notice, like directories and read them on the spot.
|
||||||
|
|
||||||
the rest is small things. the type is iosevka curly slab, hosted here and not pulled from anyone. the cursor is drawn pixel by pixel. there's a dark mode if you type dark. nothing is tracked, no cookies, no analytics.
|
the rest is small things. the type is iosevka, hosted here and not pulled from anyone. the cursor is drawn pixel by pixel. there's a dark mode if you type dark. nothing is tracked, no cookies, no analytics.
|
||||||
|
|
||||||
it follows the same idea as [openbureau](/openbureau/): own your tools, keep your formats readable. the content is cc by-sa, hugo and the theme are open source (the [legal notice](/legal-notice/) has the details), and the site builds into a docker image so anyone can run their own copy.
|
it follows the same idea as [openbureau](/about/): own your tools, keep your formats readable. the content is cc by-sa, hugo and the theme are open source (the [legal notice](/legal-notice/) has the details), and the site builds into a docker image so anyone can run their own copy.
|
||||||
|
|
||||||
written between the rhine valley and lucerne.
|
written between the rhine valley and lucerne.
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ build:
|
|||||||
**DOSSIER**\
|
**DOSSIER**\
|
||||||
2026
|
2026
|
||||||
|
|
||||||
ein rhino-plugin für die frühe phase des architektonischen entwurfs. wände, türen, fenster, decken, treppen, geschosse und eine 2d-zeichenebene, die sich wie eine 2d-zeichenebene anfühlt. native rhino-objekte, keine parallele datenbank zum abgleichen. was es gibt, ist entweder teure proprietäre software oder vollständige bim-systeme, gebaut für die dokumentationsphasen im büromassstab, die nicht dazu passen, wie man während einer skizze tatsächlich denkt. quelloffene bim-werkzeuge leben in blender oder freecad und folgen einer 3d-zuerst-logik, nützlich, aber nicht so, wie man einen plan in 2d zeichnet. studierende und kleine büros zahlen am ende, raubkopieren oder behelfen sich im blanken rhino. dossier ist das werkzeug, das ich während meines masters und meiner eigenen arbeit selbst nutzen wollte. warum rhino und nicht ein vollständig quelloffenes cad? rhino verkauft dauerlizenzen, die man wirklich besitzt, statt abonnements, die man mietet, und seine dateiformate (3dm, opennurbs) und apis (rhinocommon, eto.forms) sind gut dokumentiert und auch ausserhalb der anwendung lesbar. das ist keine freie software, aber eine gangbare position zu eigentum und offenen daten, und dort, wo manche architekten ohnehin schon arbeiten. freecad oder librecad wären philosophisch offener, aber ihre verbreitung in der architektur ist marginal. dossier trifft die disziplin dort, wo sie steht. offene daten, eine etablierte gemeinschaft, genug infrastruktur, um darauf aufzubauen.
|
ein cad für die frühe phase des architektonischen entwurfs. wände, türen, fenster, decken, treppen, dächer, geschosse und eine 2d-zeichenebene, die sich wie eine 2d-zeichenebene anfühlt. ein semantisches modell als einzige quelle der wahrheit — grundriss, schnitt, ansicht und 3d-ansicht werden alle daraus abgeleitet, nicht als separate zeichnungen von hand synchron gehalten. begonnen als rhino-plugin, mittlerweile eine eigenständige desktop-anwendung: eigener geometrie-kernel, eigene rendering-engine, eigenes dateiformat, keine abhängigkeit von software, die man nicht besitzt. was es gibt, ist entweder teure proprietäre software oder vollständige bim-systeme, gebaut für die dokumentationsphasen im büromassstab, die nicht dazu passen, wie man während einer skizze tatsächlich denkt. quelloffene bim-werkzeuge leben in blender oder freecad und folgen einer 3d-zuerst-logik, nützlich, aber nicht so, wie man einen plan in 2d zeichnet. dossier ist das werkzeug, das ich während meines masters und meiner eigenen arbeit selbst nutzen wollte. offene daten, agpl-lizenziert, teil der openbureau-suite.
|
||||||
|
|
||||||
[repo](https://dossier.openbureau.ch)
|
[repo](https://git.openbureau.ch/karim/DOSSIER-STANDALONE)
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ build:
|
|||||||
**DOSSIER**\
|
**DOSSIER**\
|
||||||
2026
|
2026
|
||||||
|
|
||||||
a rhino plugin for early-phase architectural design. walls, doors, windows, slabs, stairs, levels, and a 2d drawing layer that feels like a 2d drawing layer. native rhino objects, no parallel database to sync. what exists is either expensive proprietary software, or full bim systems built for office-scale documentation phases that don't fit how you actually think during a sketch. open source bim tools live in blender or freecad and follow a 3d-first logic — useful, but not how you 2d draw a plan. students and small offices end up either paying, pirating, or working around in plain rhino. dossier is the tool i wanted to use myself during my master's and my own work. why rhino and not a fully open source cad? rhino sells perpetual licences you actually own instead of subscriptions you rent, and its file formats (3dm, opennurbs) and apis (rhinocommon, eto.forms) are well documented and readable outside the application. that's not free software, but it's a workable position on ownership and open data and where some architects already work. freecad or librecad would be more philosophically open, but adoption in architecture is marginal. dossier meets the discipline where it is. open data, established community, enough infrastructure to build on.
|
a cad for early-phase architectural design. walls, doors, windows, slabs, stairs, roofs, levels, and a 2d drawing layer that feels like a 2d drawing layer. one semantic model as the single source of truth — plan, section, elevation, and 3d view are all derived from it, not separate drawings kept in sync by hand. it started as a rhino plugin; it's now its own standalone desktop app, with its own geometry kernel, rendering engine, and file format — no dependency on software you don't own. what exists is either expensive proprietary software, or full bim systems built for office-scale documentation that don't fit how you actually think during a sketch. open source bim tools live in blender or freecad and follow a 3d-first logic — useful, but not how you draw a plan in 2d. dossier is the tool i wanted to use myself, during my master's and in my own work. open data, agpl-licensed, part of the openbureau suite.
|
||||||
|
|
||||||
[repo](https://dossier.openbureau.ch)
|
[repo](https://git.openbureau.ch/karim/DOSSIER-STANDALONE)
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ build:
|
|||||||
**RAPPORT**\
|
**RAPPORT**\
|
||||||
2026
|
2026
|
||||||
|
|
||||||
ein verwaltungswerkzeug für kleine architekturbüros. projektverwaltung, zeiterfassung, offerten und rechnungen, buchhaltung, personaldaten, lohn, sitzungsprotokolle. alles an einem ort statt fünf zusammengeflickter abonnements. was es gibt, ist entweder zu klein (einzweck-apps, die nicht miteinander reden) oder zu gross (unternehmenssuiten für büros mit hundert leuten, mit passendem budget dazu). kleine büros zahlen am ende für software, die für jemand anderen entworfen wurde, und füllen die lücken mit tabellen. rapport ist das werkzeug, das ich selbst nutzen wollte. freie software, selbst hostbar, keine lizenz pro arbeitsplatz. deine daten bleiben auf deinen geräten.
|
ein verwaltungswerkzeug für kleine architekturbüros: projektverwaltung, zeiterfassung, offerten und rechnungen, buchhaltung, personaldaten, lohn, sitzungsprotokolle — alles an einem ort statt fünf zusammengeflickter abonnements. freie software, selbst hostbar, keine lizenz pro arbeitsplatz. deine daten bleiben auf deinen eigenen geräten.
|
||||||
|
|
||||||
[repo](https://rapport.openbureau.ch)
|
[repo](https://git.openbureau.ch/karim/RAPPORT)
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ build:
|
|||||||
**RAPPORT**\
|
**RAPPORT**\
|
||||||
2026
|
2026
|
||||||
|
|
||||||
a studio management tool for small architecture offices. project management, time tracking, quotes and invoicing, bookkeeping, employee records, payroll, meeting minutes. all in one place instead of five subscriptions patched together. what exists is either too small (single-purpose apps that don't talk to each other) or too big (enterprise suites built for offices of a hundred people, with a budget to match). small practices end up paying for software designed for someone else and filling the gaps with spreadsheets. rapport is the tool i wanted to use myself. free software, self-hostable, no per-seat licensing. your data stays on your devices.
|
a management tool for small architecture offices: project management, time tracking, quotes and invoicing, bookkeeping, employee records, payroll, meeting minutes — one place instead of five subscriptions stitched together. free software, self-hostable, no per-seat licensing. your data stays on your own devices.
|
||||||
|
|
||||||
[repo](https://rapport.openbureau.ch)
|
[repo](https://git.openbureau.ch/karim/RAPPORT)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
title: "openbureau"
|
title: "openbureau"
|
||||||
description: "offene und selbst besessene werkzeuge für die architektur"
|
description: "offene und selbst besessene werkzeuge für die architektur"
|
||||||
layout: "openbureau"
|
build:
|
||||||
|
render: never
|
||||||
|
list: never
|
||||||
---
|
---
|
||||||
|
|
||||||
architektur läuft auf werkzeugen. die werkzeuge formen, was gedacht, gezeichnet und gebaut wird. die meisten sind proprietär: abonnements, die gekündigt werden können, dateiformate, die nur ihre eigentümer lesen können, arbeit, die verschwindet, wenn eine lizenz ausläuft. in einem beruf, der ohnehin auf prekärer arbeit läuft, ist das mietabschöpfung.
|
architektur läuft auf werkzeugen. die werkzeuge formen, was gedacht, gezeichnet und gebaut wird. die meisten sind proprietär: abonnements, die gekündigt werden können, dateiformate, die nur ihre eigentümer lesen können, arbeit, die verschwindet, wenn eine lizenz ausläuft. in einem beruf, der ohnehin auf prekärer arbeit läuft, ist das mietabschöpfung.
|
||||||
|
|
||||||
freie und quelloffene software ist die sauberste antwort, aber nicht die einzige. worum es mir geht, ist eigentum und offenheit. werkzeuge, die du besitzt, statt sie zu mieten. dateiformate, die jeder lesen kann. daten, die nicht hinter einer bezahlschranke liegen. code als gemeingut, wo möglich. wo nicht, wenigstens offene daten.
|
freie und quelloffene software ist die sauberste antwort, aber nicht die einzige. worum es mir geht, ist eigentum und offenheit. werkzeuge, die du besitzt, statt sie zu mieten. dateiformate, die jeder lesen kann. daten, die nicht hinter einer bezahlschranke liegen. code als gemeingut, wo möglich. wo nicht, wenigstens offene daten.
|
||||||
|
|
||||||
openbureau ist der ort, an dem ich sammle, was ich in dieser richtung baue. kleine werkzeuge, für die eigene praxis geschrieben, veröffentlicht, damit andere sie nutzen oder forken können.
|
openbureau ist diese haltung, zu einer plattform geworden — noch im entstehen. dossier und rapport sind die ersten stücke, die darauf aufbauen: werkzeuge für die eigene praxis geschrieben, veröffentlicht, damit andere sie nutzen oder forken können.
|
||||||
|
|
||||||
über das hinaus, was ich baue, ist openbureau auch eine dienstleistung: ich kuratiere quelloffene software, die keine fixkosten und keine abhängigkeit erzeugt, richte sie ein und betreue sie, damit ein studio oder ein kleines büro auf werkzeugen läuft, die es besitzt, statt auf abonnements, die es mietet.
|
über das hinaus, was ich baue, ist openbureau auch eine dienstleistung: ich kuratiere quelloffene software, die keine fixkosten und keine abhängigkeit erzeugt, richte sie ein und betreue sie, damit ein studio oder ein kleines büro auf werkzeugen läuft, die es besitzt, statt auf abonnements, die es mietet.
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
title: "openbureau"
|
title: "openbureau"
|
||||||
description: "open and self-owned tools for architecture"
|
description: "open and self-owned tools for architecture"
|
||||||
layout: "openbureau"
|
build:
|
||||||
|
render: never
|
||||||
|
list: never
|
||||||
---
|
---
|
||||||
|
|
||||||
architecture runs on tools. the tools shape what gets thought, drawn, and built. most are proprietary: subscriptions that can be cancelled, file formats only their owners can read, work that disappears when a license lapses. in a profession that already runs on precarious labor, that's rent extraction.
|
architecture runs on tools. the tools shape what gets thought, drawn, and built. most are proprietary: subscriptions that can be cancelled, file formats only their owners can read, work that disappears when a license lapses. in a profession that already runs on precarious labor, that's rent extraction.
|
||||||
|
|
||||||
free and open source software is the cleanest answer, but not the only one. what i'm after is ownership and openness. tools you own instead of rent. file formats anyone can read. data that doesn't sit behind a paywall. code as a commons when possible. when it isn't, at least open data.
|
free and open source software is the cleanest answer, but not the only one. what i'm after is ownership and openness. tools you own instead of rent. file formats anyone can read. data that doesn't sit behind a paywall. code as a commons when possible. when it isn't, at least open data.
|
||||||
|
|
||||||
openbureau is where i collect what i build along these lines. small tools, written for my own practice, released so other people can use or fork them.
|
openbureau is that position turned into a platform — still taking shape. dossier and rapport are the first pieces built on it: tools written for my own practice, released so other people can use or fork them.
|
||||||
|
|
||||||
beyond what i build, openbureau is also a service: i curate open-source software that carries no fixed costs and no lock-in, set it up, and support it, so a studio or small practice can run on tools it owns instead of subscriptions it rents.
|
beyond what i build, openbureau is also a service: i curate open-source software that carries no fixed costs and no lock-in, set it up, and support it, so a studio or small practice can run on tools it owns instead of subscriptions it rents.
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
title: "portfolio"
|
title: "portfolio"
|
||||||
description: "ausgewählte projekte"
|
description: "ausgewählte projekte"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
studien- und semesterarbeiten aus architektur und gestaltung, neueste zuerst. projekt öffnen für bilder, pläne und einen kurzen text zur idee dahinter.
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
title: "portfolio"
|
title: "portfolio"
|
||||||
description: "selected projects"
|
description: "selected projects"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
student and studio work in architecture and design, newest first. open a project for images, plans, and a short text on the idea behind it.
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ images:
|
|||||||
- { src: "/img/sem02/04.webp", name: "front-elevation.webp" }
|
- { src: "/img/sem02/04.webp", name: "front-elevation.webp" }
|
||||||
- { src: "/img/sem02/05.webp", name: "side-elevation.webp" }
|
- { src: "/img/sem02/05.webp", name: "side-elevation.webp" }
|
||||||
- { src: "/img/sem02/06.webp", name: "joint-axonometric.webp" }
|
- { src: "/img/sem02/06.webp", name: "joint-axonometric.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
im zentrum dieses studienprojekts steht die konstruktive auseinandersetzung mit dem holzknoten, nicht als statisches detail, sondern als räumlich wirksames system. ausgehend von einem grundknoten, der ohne diagonale aussteifung auskommt und allein auf horizontalen und vertikalen balken beruht, wurde ein räumliches prinzip entwickelt, das zugleich rational und atmosphärisch ist. die besonderheit liegt in seiner skalierbarkeit: derselbe knoten wird in unterschiedlichen massstäben angewendet, vom feinen raster der fassadenstruktur bis zur tragenden ordnung des zweigeschossigen baus. so entsteht ein durchgehendes konstruktives system, das den ganzen bau trägt und sich zugleich sichtbar macht. das dojo ist um einen offenen innenhof organisiert. die klare holzkonstruktion vermittelt zwischen offenheit und konzentration, zwischen leichtigkeit und strenge, eigenschaften, die sich aus den kampfkünsten ableiten. der entwurf versteht sich als architektonische reflexion über struktur, disziplin und räumliche wahrnehmung.
|
im zentrum dieses entwurfs steht die konstruktive auseinandersetzung mit dem holzknoten – nicht als statisches detail, sondern als räumlich wirksames system. ausgehend von einem grundknoten, der ohne diagonale aussteifungen auskommt und ausschliesslich mit horizontalen und vertikalen balken funktioniert, wurde ein räumliches prinzip entwickelt, das zugleich rational und atmosphärisch ist.
|
||||||
|
|
||||||
|
die besonderheit liegt in der skalierbarkeit: der identische knoten wird in verschiedenen massstäben eingesetzt – vom feinen raster der fassadenstruktur bis zur tragenden ordnung des zweigeschossigen gebäudes. das dojo ist um einen offenen innenhof organisiert.
|
||||||
|
|
||||||
|
die klare holzkonstruktion vermittelt zwischen offenheit und konzentration, zwischen leichtigkeit und strenge – qualitäten, die aus der kampfkunst abgeleitet wurden. der entwurf versteht sich als architektonische reflexion über struktur, disziplin und raumwahrnehmung.
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ images:
|
|||||||
- { src: "/img/sem02/04.webp", name: "front-elevation.webp" }
|
- { src: "/img/sem02/04.webp", name: "front-elevation.webp" }
|
||||||
- { src: "/img/sem02/05.webp", name: "side-elevation.webp" }
|
- { src: "/img/sem02/05.webp", name: "side-elevation.webp" }
|
||||||
- { src: "/img/sem02/06.webp", name: "joint-axonometric.webp" }
|
- { src: "/img/sem02/06.webp", name: "joint-axonometric.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
at the centre of this student project is the constructive exploration of the timber joint, not as a static detail but as a spatially effective system. starting from a basic joint that works without diagonal bracing and relies solely on horizontal and vertical beams, a spatial principle was developed that is both rational and atmospheric. the particularity lies in its scalability: the identical joint is applied at different scales, from the fine grid of the facade structure to the load-bearing order of the two-storey building. this creates a continuous constructive system that carries the entire building while simultaneously making itself visible. the dojo is organised around an open inner courtyard. the clear timber construction mediates between openness and concentration, between lightness and rigour, qualities derived from the martial arts. the design understands itself as an architectural reflection on structure, discipline, and spatial perception.
|
at the centre of this design is the constructive exploration of the timber joint — not as a static detail, but as a spatially effective system. starting from a basic joint that works without diagonal bracing, relying solely on horizontal and vertical beams, a spatial principle was developed that is both rational and atmospheric.
|
||||||
|
|
||||||
|
the particularity lies in its scalability: the identical joint is applied at different scales, from the fine grid of the facade structure to the load-bearing order of the two-storey building. the dojo is organised around an open inner courtyard.
|
||||||
|
|
||||||
|
the clear timber construction mediates between openness and concentration, between lightness and rigour — qualities derived from the martial arts. the design understands itself as an architectural reflection on structure, discipline, and spatial perception.
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "hotel national"
|
||||||
|
slug: "hotel-national"
|
||||||
|
date: 2026-04-01
|
||||||
|
thumbnail: "/img/hotel-national/entrance-model.webp"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ma"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/hotel-national/entrance-model.webp", name: "entrance-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/staircase-model.webp", name: "staircase-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/reading-room-model.webp", name: "reading-room-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-full-ground-floor.webp", name: "plan-full-ground-floor.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-intervention-near.webp", name: "plan-intervention-near.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-intervention-detail.webp", name: "plan-intervention-detail.webp" }
|
||||||
|
categories: ["design"]
|
||||||
|
---
|
||||||
|
|
||||||
|
in zusammenarbeit mit tamara grabovska basiert die zusammenführung des hotel national mit der lotte bibliothek auf einem bewusst gesetzten widerspruch. ein grandhotel mit klassischer repräsentationsarchitektur trifft auf eine linksautonome bibliothek. statt diesen gegensatz zu nivellieren, wird er räumlich inszeniert – als produktive koexistenz zweier haltungen innerhalb desselben gebäudes.
|
||||||
|
|
||||||
|
der eingriff konzentriert sich auf eine ecke des historischen, mit pilastern gegliederten baukörpers. durch das freistellen einer säule entsteht eine präzise gesetzte leerstelle. zwei wände werden entfernt, das hochparterre im bereich des einschnitts auf terrainhöhe abgesenkt. so bildet sich ein überdachter aussenraum – getragen von einer einzelnen säule –, der als schwelle, durchgang und adresse zugleich fungiert.
|
||||||
|
|
||||||
|
die bibliothek erhält damit einen eigenständigen zugang und kann autonom im hotel stattfinden, ohne sich unterzuordnen. der eingriff ist minimal, aber wirkungsvoll: eine räumliche öffnung statt einer baulichen addition. innen und aussen stehen in direkter beziehung, ohne ihre eigenständigkeit aufzugeben.
|
||||||
|
|
||||||
|
das projekt versteht sich als sanfte besetzung: kein radikaler umbau, sondern eine präzise verschiebung bestehender strukturen. durch minimale eingriffe entsteht ein ort, der differenz nicht kaschiert, sondern sichtbar macht.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "hotel national"
|
||||||
|
slug: "hotel-national"
|
||||||
|
date: 2026-04-01
|
||||||
|
thumbnail: "/img/hotel-national/entrance-model.webp"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ma"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/hotel-national/entrance-model.webp", name: "entrance-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/staircase-model.webp", name: "staircase-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/reading-room-model.webp", name: "reading-room-model.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-full-ground-floor.webp", name: "plan-full-ground-floor.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-intervention-near.webp", name: "plan-intervention-near.webp" }
|
||||||
|
- { src: "/img/hotel-national/plan-intervention-detail.webp", name: "plan-intervention-detail.webp" }
|
||||||
|
categories: ["design"]
|
||||||
|
---
|
||||||
|
|
||||||
|
in collaboration with tamara grabovska, merging the hotel national with the lotte library is based on a deliberately set contradiction. a grand hotel with classical representational architecture meets a left-autonomous library. rather than levelling this opposition, it is staged spatially — as a productive coexistence of two attitudes within the same building.
|
||||||
|
|
||||||
|
the intervention concentrates on one corner of the historic, pilaster-articulated volume. freeing a column creates a precisely placed void. two walls are removed, and the raised ground floor is lowered to grade in the area of the incision. this forms a covered outdoor space — carried by a single column — that functions simultaneously as threshold, passage, and address.
|
||||||
|
|
||||||
|
the library thereby gains an independent entrance and can operate autonomously within the hotel, without being subordinated to it. the intervention is minimal but effective: a spatial opening rather than a built addition. interior and exterior stand in direct relation without giving up their independence.
|
||||||
|
|
||||||
|
the project understands itself as a gentle occupation: not a radical conversion, but a precise displacement of existing structures. through minimal interventions, a place emerges that does not conceal difference but makes it visible.
|
||||||
@@ -16,6 +16,11 @@ images:
|
|||||||
- { src: "/img/sem01/05.webp", name: "cutaway-model-angle.webp" }
|
- { src: "/img/sem01/05.webp", name: "cutaway-model-angle.webp" }
|
||||||
- { src: "/img/sem01/06.webp", name: "site-plan.webp" }
|
- { src: "/img/sem01/06.webp", name: "site-plan.webp" }
|
||||||
- { src: "/img/sem01/07.webp", name: "street-linocut.webp" }
|
- { src: "/img/sem01/07.webp", name: "street-linocut.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
tonhalle museo ist ein spekulatives studienprojekt an der schnittstelle von architektur, klang und theorie. zwei scheinbar widersprüchliche programme, konzertsaal und museum, treffen in einem einzigen gebäude zusammen. dieses zusammentreffen wird nicht harmonisiert, sondern bewusst verschärft: unterschiedliche räumliche logiken, nutzungsdynamiken und atmosphären prallen aufeinander und erzeugen ein spannungsfeld, das den architektonischen ausdruck prägt. der entwurf beruht auf einer auditiven analyse, eine fünfminütige klangsequenz diente als ausgangspunkt für die räumliche struktur. im fokus standen nicht funktion oder form, sondern die übersetzung von klang in räumliche dimensionen. lautstärke, rhythmus, dichte und stille wurden in proportionen, übergänge und materialität übersetzt. das ergebnis ist ein gebautes fragment, eine bewusst unvollständige idee, ein raumkörper, der nicht erklären, sondern erfahrbar werden will. tonhalle museo versteht sich als versuch, architektur durch das hören zu denken, als räumliche partitur zwischen konzentration und ablenkung, zwischen aufführung und betrachtung.
|
tonhalle museo ist ein spekulativer entwurf an der schnittstelle von architektur, klang und theorie. zwei scheinbar gegensätzliche programme – konzertsaal und museum – treffen in einem gemeinsamen baukörper aufeinander. dieses aufeinandertreffen wird nicht harmonisiert, sondern bewusst zugespitzt.
|
||||||
|
|
||||||
|
der entwurf basiert auf einer auditiven analyse: eine fünfminütige tonsequenz diente als ausgangspunkt für die entwicklung des raumgefüges. nicht funktion oder form standen im vordergrund, sondern die übersetzung von klang in räumliche dimensionen. lautstärke, rhythmus, dichte und stille wurden in raumproportionen, übergänge und materialität übersetzt.
|
||||||
|
|
||||||
|
das ergebnis ist ein gebautes fragment, ein bewusst unvollständiger gedanke – ein raumkörper, der nicht erklären, sondern erfahrbar machen will. tonhalle museo versteht sich als versuch, architektur aus dem hören zu denken.
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ images:
|
|||||||
- { src: "/img/sem01/05.webp", name: "cutaway-model-angle.webp" }
|
- { src: "/img/sem01/05.webp", name: "cutaway-model-angle.webp" }
|
||||||
- { src: "/img/sem01/06.webp", name: "site-plan.webp" }
|
- { src: "/img/sem01/06.webp", name: "site-plan.webp" }
|
||||||
- { src: "/img/sem01/07.webp", name: "street-linocut.webp" }
|
- { src: "/img/sem01/07.webp", name: "street-linocut.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
tonhalle museo is a speculative student project at the intersection of architecture, sound, and theory. two seemingly contradictory programmes, concert hall and museum, converge within a single building. this convergence is not harmonised but deliberately intensified: different spatial logics, usage dynamics, and atmospheres collide, creating a field of tension that shapes the architectural expression. the design is based on an auditory analysis, a five-minute sound sequence served as the starting point for the spatial structure. the focus was not on function or form, but on the translation of sound into spatial dimensions. volume, rhythm, density, and silence were translated into proportions, transitions, and materiality. the result is a built fragment, a deliberately incomplete idea, a spatial body that does not seek to explain, but rather to make itself experiential. tonhalle museo understands itself as an attempt to conceive architecture through listening, as a spatial score between concentration and distraction, between performance and contemplation.
|
tonhalle museo is a speculative design at the intersection of architecture, sound, and theory. two seemingly contradictory programmes — concert hall and museum — meet within a single building. this convergence is not harmonised but deliberately sharpened.
|
||||||
|
|
||||||
|
the design is based on an auditory analysis: a five-minute sound sequence served as the starting point for developing the spatial structure. the focus was not on function or form, but on translating sound into spatial dimensions. volume, rhythm, density, and silence were translated into spatial proportions, transitions, and materiality.
|
||||||
|
|
||||||
|
the result is a built fragment, a deliberately incomplete thought — a spatial body that does not seek to explain but to be experienced. tonhalle museo understands itself as an attempt to think architecture from listening.
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "object oriented ontology"
|
||||||
|
slug: "object-oriented-ontology"
|
||||||
|
date: 2026-01-15
|
||||||
|
thumbnail: "/img/ooo/thumbnail.webp"
|
||||||
|
video: "/media/ooo.mp4"
|
||||||
|
categories: ["film"]
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "object oriented ontology"
|
||||||
|
slug: "object-oriented-ontology"
|
||||||
|
date: 2026-01-15
|
||||||
|
thumbnail: "/img/ooo/thumbnail.webp"
|
||||||
|
video: "/media/ooo.mp4"
|
||||||
|
categories: ["film"]
|
||||||
|
---
|
||||||
@@ -26,4 +26,5 @@ images:
|
|||||||
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-04.webp", name: "rozzol-melara-04.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-04.webp", name: "rozzol-melara-04.webp" }
|
||||||
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-05.webp", name: "rozzol-melara-05.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-05.webp", name: "rozzol-melara-05.webp" }
|
||||||
- { src: "/img/studytrip-trieste/Triest_Stanjel-01.webp", name: "stanjel.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Stanjel-01.webp", name: "stanjel.webp" }
|
||||||
|
categories: ["photo"]
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ images:
|
|||||||
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-04.webp", name: "rozzol-melara-04.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-04.webp", name: "rozzol-melara-04.webp" }
|
||||||
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-05.webp", name: "rozzol-melara-05.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Rozzol_Melara-05.webp", name: "rozzol-melara-05.webp" }
|
||||||
- { src: "/img/studytrip-trieste/Triest_Stanjel-01.webp", name: "stanjel.webp" }
|
- { src: "/img/studytrip-trieste/Triest_Stanjel-01.webp", name: "stanjel.webp" }
|
||||||
|
categories: ["photo"]
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ images:
|
|||||||
- { src: "/img/sem05/05.webp", name: "dwelling-interior.webp" }
|
- { src: "/img/sem05/05.webp", name: "dwelling-interior.webp" }
|
||||||
- { src: "/img/sem05/section01.webp", name: "long-section.webp" }
|
- { src: "/img/sem05/section01.webp", name: "long-section.webp" }
|
||||||
- { src: "/img/sem05/section02.webp", name: "hall-section.webp" }
|
- { src: "/img/sem05/section02.webp", name: "hall-section.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
die séchoir-halle ist ein studienprojekt, das die themen handel, bestand und weiternutzung innerhalb ehemaliger industriearchitektur untersucht. ein ehemaliges industriegebäude, verwurzelt in der lokalen textilgeschichte, wird in seiner identität bewahrt und zugleich mit einem neuen programm versehen. wo einst viskosestoffe zum trocknen aufgehängt wurden, werden heute neue bausteine aus recycelten textilien produziert, tragende elemente, aus alten stoffen in einem stampfverfahren gefertigt, nach den prinzipien der kreislaufwirtschaft und der wiederverwendung von material. der bestand wird erhalten und über einer hälfte der halle um ein zusätzliches geschoss ergänzt, das seine rohe, funktionale atmosphäre fortführt, statt sie zu ersetzen. neue wohneinheiten in direkter nachbarschaft treten in dialog mit der industriearchitektur und schaffen ein gemischtes quartier mit produktiven, sozialen und wohnfunktionen.
|
die séchoir-halle, ein ehemaliger industriebau mit ursprung in der lokalen textilgeschichte, wurde in ihrer identität bewahrt und zugleich neu programmiert. wo früher viskosestoffe zum trocknen aufgehängt wurden, entstehen heute neue bausteine aus recycelten textilien. aus alten stoffen werden im stampfverfahren tragende elemente gefertigt – ein konstruktives system auf basis von kreislaufprinzipien.
|
||||||
|
|
||||||
|
die halle selbst wurde nur minimal verändert. ihre bestehende struktur bleibt erhalten und wird durch eine aufstockung über der einen hallenhälfte ergänzt. die intervention zielt nicht auf eine komplette neugestaltung, sondern auf eine weiterführung der bestehenden atmosphäre – roh, funktional, identitätsstark.
|
||||||
|
|
||||||
|
ergänzend wurden neue wohneinheiten in direkter nachbarschaft zur halle geschaffen. sie stehen in bewusstem dialog zur industriearchitektur und schaffen ein gemischtes quartier mit produktiven, sozialen und wohnwirtschaftlichen nutzungen.
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ images:
|
|||||||
- { src: "/img/sem05/05.webp", name: "dwelling-interior.webp" }
|
- { src: "/img/sem05/05.webp", name: "dwelling-interior.webp" }
|
||||||
- { src: "/img/sem05/section01.webp", name: "long-section.webp" }
|
- { src: "/img/sem05/section01.webp", name: "long-section.webp" }
|
||||||
- { src: "/img/sem05/section02.webp", name: "hall-section.webp" }
|
- { src: "/img/sem05/section02.webp", name: "hall-section.webp" }
|
||||||
|
categories: ["design"]
|
||||||
---
|
---
|
||||||
|
|
||||||
the séchoir-halle is a student project exploring the themes of commerce, existing structures, and adaptive reuse within former industrial architecture. a former industrial building rooted in local textile history is preserved in its identity while being given a new programme. where viscose fabrics were once hung to dry, new building blocks made from recycled textiles are produced today — load-bearing elements crafted from old fabrics using a ramming process, based on circular economy principles and material reuse. the existing structure is retained and complemented by an additional storey above one half of the hall, continuing its raw, functional atmosphere rather than replacing it. new residential units in direct proximity engage in dialogue with the industrial architecture, creating a mixed-use quarter with productive, social, and residential functions.
|
the séchoir-halle, a former industrial building rooted in the local textile history, was preserved in its identity while being given a new programme. where viscose fabrics were once hung to dry, new building blocks made from recycled textiles are produced today — load-bearing elements crafted from old fabrics through a ramming process, a constructive system based on circular principles.
|
||||||
|
|
||||||
|
the hall itself was altered only minimally. its existing structure is retained and complemented by an additional storey above one half of the hall. the intervention aims not at a complete redesign but at a continuation of the existing atmosphere — raw, functional, strongly identifiable.
|
||||||
|
|
||||||
|
new residential units were added in direct proximity to the hall. they stand in deliberate dialogue with the industrial architecture, forming a mixed quarter with productive, social, and residential uses.
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ slug: "the-shadow-over-emmen"
|
|||||||
date: 2025-01-10T09:00:00
|
date: 2025-01-10T09:00:00
|
||||||
thumbnail: "/img/shadowofemmen/thumbnail.webp"
|
thumbnail: "/img/shadowofemmen/thumbnail.webp"
|
||||||
video: "/media/shadow.mp4"
|
video: "/media/shadow.mp4"
|
||||||
|
categories: ["film"]
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ slug: "the-shadow-over-emmen"
|
|||||||
date: 2025-01-10T09:00:00
|
date: 2025-01-10T09:00:00
|
||||||
thumbnail: "/img/shadowofemmen/thumbnail.webp"
|
thumbnail: "/img/shadowofemmen/thumbnail.webp"
|
||||||
video: "/media/shadow.mp4"
|
video: "/media/shadow.mp4"
|
||||||
|
categories: ["film"]
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: "translation"
|
||||||
|
slug: "translation"
|
||||||
|
date: 2026-02-01
|
||||||
|
thumbnail: "/img/translation/grid-collage.webp"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ma"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/translation/grid-collage.webp", name: "grid-collage.webp" }
|
||||||
|
categories: ["design", "text"]
|
||||||
|
---
|
||||||
|
|
||||||
|
in der zusammenarbeit mit tim schwander und jérôme arnold entsteht eine auseinandersetzung mit architektonischen denkweisen, die weniger zitiert als vielmehr übersetzt werden. die arbeit versteht sich als experimentierfeld, in dem ordnungssysteme, landschaft und fragment sich gegenseitig verschieben.
|
||||||
|
|
||||||
|
ein durchgehendes raster legt sich über die gesamte komposition – inspiriert vom metaphorischen arbeiten von oswald mathias ungers. das grid definiert zunächst klarheit und systematik, verliert jedoch durch überlagerung und massstabsverschiebung seine eindeutigkeit. mass wird relativ, hierarchien lösen sich auf.
|
||||||
|
|
||||||
|
die räumlichen elemente sind isometrisch gesetzt und erzeugen eine additive tiefenwirkung ohne klassischen fluchtpunkt. fläche wird zu topografie, struktur zu oberfläche – eine architektonische dialektik zwischen system und ort, typologie und situation.
|
||||||
|
|
||||||
|
so entsteht keine abgeschlossene komposition, sondern ein offenes system: fragmentiert, geschichtet, widersprüchlich – und gerade dadurch kohärent.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: "translation"
|
||||||
|
slug: "translation"
|
||||||
|
date: 2026-02-01
|
||||||
|
thumbnail: "/img/translation/grid-collage.webp"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ma"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/translation/grid-collage.webp", name: "grid-collage.webp" }
|
||||||
|
categories: ["design", "text"]
|
||||||
|
---
|
||||||
|
|
||||||
|
in collaboration with tim schwander and jérôme arnold, this work engages with architectural ways of thinking that are less quoted than translated. it understands itself as a field of experimentation in which systems of order, landscape, and fragment shift against one another.
|
||||||
|
|
||||||
|
a continuous grid lies over the entire composition — inspired by the metaphorical working method of oswald mathias ungers. the grid initially defines clarity and system, but loses its precision through overlay and shifts in scale. measure becomes relative, hierarchies dissolve.
|
||||||
|
|
||||||
|
the spatial elements are set isometrically, producing an additive sense of depth without a classical vanishing point. surface becomes topography, structure becomes surface — an architectural dialectic between system and place, typology and situation.
|
||||||
|
|
||||||
|
what emerges is not a closed composition but an open system: fragmented, layered, contradictory — and coherent precisely because of it.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "utopie der koexistenz"
|
||||||
|
slug: "utopie-der-koexistenz"
|
||||||
|
date: 2025-06-01
|
||||||
|
thumbnail: "/img/utopie-der-koexistenz/courtyard-render.webp"
|
||||||
|
studies: "/studies/hslu/ba/semester_06"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ba"]
|
||||||
|
semesters: ["semester_06"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-render.webp", name: "courtyard-render.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-facade-model.webp", name: "courtyard-facade-model.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/context-collage.webp", name: "context-collage.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/facade-detail-model.webp", name: "facade-detail-model.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-overview-render.webp", name: "courtyard-overview-render.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/site-plan-ground-floor.webp", name: "site-plan-ground-floor.webp" }
|
||||||
|
categories: ["design"]
|
||||||
|
---
|
||||||
|
|
||||||
|
vor dem hintergrund der funktionalen krise vieler spitäler aus den 1960er- und 70er-jahren setzt diese bachelorarbeit auf eine weiterentwicklung des bestehenden kantonsspitals baden anstelle eines radikalen abrisses. der ursprünglich vorgesehene komplettneubau wird infrage gestellt – zugunsten einer strategie, die brüche, übergänge und reibungen nicht glättet, sondern produktiv macht.
|
||||||
|
|
||||||
|
neue volumen treten in einen dialog mit dem bestand: bewusst heterogen in massstab, haltung und typologie. höhere körper, kleinere anbauten, ergänzende zwischenräume – sie alle finden ihren platz in einem ensemble, das unterschiedlichkeit nicht negiert, sondern als qualität begreift.
|
||||||
|
|
||||||
|
so entsteht eine räumliche collage, die nicht auf formale einheit abzielt, sondern auf eine vielstimmige verdichtung. das areal wird zum offenen system, in dem geschichte sichtbar bleibt und fortgeschrieben wird.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: "utopie der koexistenz"
|
||||||
|
slug: "utopie-der-koexistenz"
|
||||||
|
date: 2025-06-01
|
||||||
|
thumbnail: "/img/utopie-der-koexistenz/courtyard-render.webp"
|
||||||
|
studies: "/studies/hslu/ba/semester_06"
|
||||||
|
schools: ["hslu"]
|
||||||
|
degrees: ["ba"]
|
||||||
|
semesters: ["semester_06"]
|
||||||
|
images:
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-render.webp", name: "courtyard-render.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-facade-model.webp", name: "courtyard-facade-model.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/context-collage.webp", name: "context-collage.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/facade-detail-model.webp", name: "facade-detail-model.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/courtyard-overview-render.webp", name: "courtyard-overview-render.webp" }
|
||||||
|
- { src: "/img/utopie-der-koexistenz/site-plan-ground-floor.webp", name: "site-plan-ground-floor.webp" }
|
||||||
|
categories: ["design"]
|
||||||
|
---
|
||||||
|
|
||||||
|
against the backdrop of the functional crisis affecting many hospitals built in the 1960s and 70s, this bachelor thesis pursues a development of the existing kantonsspital baden rather than a radical demolition. the originally planned complete new build is called into question — in favour of a strategy that does not smooth over ruptures, transitions, and frictions, but makes them productive.
|
||||||
|
|
||||||
|
new volumes enter into dialogue with the existing structure: deliberately heterogeneous in scale, attitude, and typology. taller bodies, smaller additions, and connecting in-between spaces all find their place within an ensemble that does not deny difference but understands it as a quality.
|
||||||
|
|
||||||
|
what emerges is a spatial collage that does not aim for formal unity but for a many-voiced density. the site becomes an open system in which history remains visible and continues to be written.
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
title: "studio"
|
|
||||||
description: "ruhige, typografisch präzise websites für architektur und gestaltung"
|
|
||||||
---
|
|
||||||
|
|
||||||
ich gestalte ruhige, typografisch präzise websites für menschen, die genug haben von überladenen baukasten-seiten. ich bin architekt. ich verstehe, wie man arbeit zeigt, bevor das gespräch anfängt.
|
|
||||||
|
|
||||||
**was ich mache**\
|
|
||||||
schlanke, schnelle websites, die dem gehören, der sie bezahlt. kein plugin-wildwuchs, keine monatlichen lizenzfallen, keine seite, die nach zwei jahren auseinanderfällt. gestaltung, die zurücktritt, damit die arbeit nach vorne kommt.
|
|
||||||
|
|
||||||
**warum so und nicht anders**\
|
|
||||||
die meisten kleinen websites laufen auf systemen, die mehr verwalten als gestalten. sie sind langsam, teuer im unterhalt und hängen an diensten, über die niemand mehr kontrolle hat. ich baue anders. statische seiten, die in fünf jahren noch laufen, im unterhalt fast nichts kosten und von keinem konzern abhängen. die gleiche haltung steckt hinter [openbureau](/de/openbureau/).
|
|
||||||
|
|
||||||
**für wen**\
|
|
||||||
architekturbüros, innenarchitekten, fotografen, werkstätten mit anspruch. menschen, die form ernst nehmen und eine website wollen, die das zeigt, statt es zu behaupten.
|
|
||||||
|
|
||||||
**arbeiten**\
|
|
||||||
<dl class="meta">
|
|
||||||
<dt>fund bureau</dt><dd>one-page-website für ein kollektiv von architekten in luzern · 2026 · <a href="https://fundbureau.xyz">fundbureau.xyz</a></dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**kontakt**\
|
|
||||||
wenn das nach ihrer vorstellung klingt, schreiben sie mir. ein kurzer satz dazu, wer sie sind und was sie machen, reicht für den anfang.\
|
|
||||||
[hi@karimgabrielevarano.xyz](mailto:hi@karimgabrielevarano.xyz)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
title: "studio"
|
|
||||||
description: "calm, typographically precise websites for architecture and design"
|
|
||||||
---
|
|
||||||
|
|
||||||
i make calm, typographically precise websites for people who have had enough of bloated site builders. i'm an architect. i understand how to show work before the conversation starts.
|
|
||||||
|
|
||||||
**what i do**\
|
|
||||||
lean, fast websites that belong to whoever pays for them. no plugin sprawl, no monthly licence traps, no site that falls apart in two years. design that steps back so the work comes forward.
|
|
||||||
|
|
||||||
**why this way**\
|
|
||||||
most small websites run on systems that manage more than they design. they're slow, expensive to maintain, and tied to services no one really controls anymore. i build differently. static pages that still run in five years, cost almost nothing to keep, and depend on no corporation. the same stance behind [openbureau](/openbureau/).
|
|
||||||
|
|
||||||
**who it's for**\
|
|
||||||
architecture practices, interior designers, photographers, workshops with standards. people who take form seriously and want a website that shows it instead of claiming it.
|
|
||||||
|
|
||||||
**work**\
|
|
||||||
<dl class="meta">
|
|
||||||
<dt>fund bureau</dt><dd>one-page website for a collective of architects in lucerne · 2026 · <a href="https://fundbureau.xyz">fundbureau.xyz</a></dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**contact**\
|
|
||||||
if that sounds like what you have in mind, write to me. a short line about who you are and what you do is enough to start.\
|
|
||||||
[hi@karimgabrielevarano.xyz](mailto:hi@karimgabrielevarano.xyz)
|
|
||||||
@@ -8,6 +8,7 @@ enableRobotsTXT = true
|
|||||||
school = "schools"
|
school = "schools"
|
||||||
degree = "degrees"
|
degree = "degrees"
|
||||||
semester = "semesters"
|
semester = "semesters"
|
||||||
|
category = "categories"
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
description = "architecture studio"
|
description = "architecture studio"
|
||||||
@@ -34,14 +35,6 @@ enableRobotsTXT = true
|
|||||||
name = "about"
|
name = "about"
|
||||||
pageRef = "/about"
|
pageRef = "/about"
|
||||||
weight = 2
|
weight = 2
|
||||||
[[languages.en.menu.main]]
|
|
||||||
name = "studio"
|
|
||||||
pageRef = "/studio"
|
|
||||||
weight = 3
|
|
||||||
[[languages.en.menu.main]]
|
|
||||||
name = "openbureau"
|
|
||||||
pageRef = "/openbureau"
|
|
||||||
weight = 4
|
|
||||||
|
|
||||||
[languages.de]
|
[languages.de]
|
||||||
locale = "de_CH"
|
locale = "de_CH"
|
||||||
@@ -58,14 +51,6 @@ enableRobotsTXT = true
|
|||||||
name = "about"
|
name = "about"
|
||||||
pageRef = "/about"
|
pageRef = "/about"
|
||||||
weight = 2
|
weight = 2
|
||||||
[[languages.de.menu.main]]
|
|
||||||
name = "studio"
|
|
||||||
pageRef = "/studio"
|
|
||||||
weight = 3
|
|
||||||
[[languages.de.menu.main]]
|
|
||||||
name = "openbureau"
|
|
||||||
pageRef = "/openbureau"
|
|
||||||
weight = 4
|
|
||||||
|
|
||||||
[markup]
|
[markup]
|
||||||
[markup.goldmark.renderer]
|
[markup.goldmark.renderer]
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
{{ define "bodyclass" }}page-about{{ end }}
|
{{ define "bodyclass" }}page-about{{ end }}
|
||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
{{ $cv := .Site.GetPage "/cv" }}
|
{{ $files := slice
|
||||||
|
(.Site.GetPage "/cv")
|
||||||
|
(.Site.GetPage "/openbureau")
|
||||||
|
(.Site.GetPage "/ob/rapport")
|
||||||
|
(.Site.GetPage "/ob/dossier") }}
|
||||||
|
{{ $names := slice "cv.md" "openbureau.md" "rapport.md" "dossier.md" }}
|
||||||
|
{{ $total := 0 }}{{ range $files }}{{ if . }}{{ $total = add $total 1 }}{{ end }}{{ end }}
|
||||||
<div class="proj-layout">
|
<div class="proj-layout">
|
||||||
{{ if $cv }}
|
{{ if gt $total 0 }}
|
||||||
<div class="tui tui--files filebrowser">
|
<div class="tui tui--files filebrowser">
|
||||||
<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>
|
<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>
|
||||||
<p class="tui__cmd tui__total">total 1</p>
|
<p class="tui__cmd tui__total">total {{ $total }}</p>
|
||||||
<ul class="tui__ls">
|
<ul class="tui__ls">
|
||||||
<li><a href="{{ "/" | relLangURL }}" data-name="..">
|
<li><a href="{{ "/" | relLangURL }}" data-name="..">
|
||||||
<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>
|
<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
<li><a data-name="cv.md" data-text="cv.md" class="is-selected">
|
{{ $first := true }}
|
||||||
<span class="tui__perm">-rw-r--r--</span><span class="tui__name">cv.md</span>
|
{{ range $i, $p := $files }}{{ if $p }}
|
||||||
|
<li><a data-name="{{ index $names $i }}" data-text="{{ index $names $i }}"{{ if $first }} class="is-selected"{{ end }}>
|
||||||
|
<span class="tui__perm">-rw-r--r--</span><span class="tui__name">{{ index $names $i }}</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
{{ $first = false }}
|
||||||
|
{{ end }}{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<figure class="tui__preview">
|
<figure class="tui__preview">
|
||||||
<div class="tui__text" data-text="cv.md">{{ $cv.Content }}</div>
|
{{ $first := true }}
|
||||||
<figcaption class="tui__status">cv.md</figcaption>
|
{{ range $i, $p := $files }}{{ if $p }}
|
||||||
|
<div class="tui__text" data-text="{{ index $names $i }}"{{ if not $first }} hidden{{ end }}>{{ $p.Content }}</div>
|
||||||
|
{{ $first = false }}
|
||||||
|
{{ end }}{{ end }}
|
||||||
|
<figcaption class="tui__status">{{ range $i, $p := $files }}{{ if $p }}{{ index $names $i }}{{ break }}{{ end }}{{ end }}</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
{{ define "bodyclass" }}page-openbureau{{ end }}
|
|
||||||
{{ define "main" }}
|
|
||||||
{{ $rapport := .Site.GetPage "/ob/rapport" }}
|
|
||||||
{{ $dossier := .Site.GetPage "/ob/dossier" }}
|
|
||||||
<div class="tui tui--files filebrowser">
|
|
||||||
<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>
|
|
||||||
{{ $total := 1 }}{{ if $rapport }}{{ $total = add $total 1 }}{{ end }}{{ if $dossier }}{{ $total = add $total 1 }}{{ end }}
|
|
||||||
<p class="tui__cmd tui__total">total {{ $total }}</p>
|
|
||||||
<ul class="tui__ls">
|
|
||||||
<li><a href="{{ "/" | relLangURL }}" data-name="..">
|
|
||||||
<span class="tui__perm">drwxr-xr-x</span><span class="tui__name">..</span>
|
|
||||||
</a></li>
|
|
||||||
<li><a data-name="info.md" data-text="info.md" class="is-selected">
|
|
||||||
<span class="tui__perm">-rw-r--r--</span><span class="tui__name">info.md</span>
|
|
||||||
</a></li>
|
|
||||||
{{ if $rapport }}
|
|
||||||
<li><a data-name="rapport.md" data-text="rapport.md">
|
|
||||||
<span class="tui__perm">-rw-r--r--</span><span class="tui__name">rapport.md</span>
|
|
||||||
</a></li>
|
|
||||||
{{ end }}
|
|
||||||
{{ if $dossier }}
|
|
||||||
<li><a data-name="dossier.md" data-text="dossier.md">
|
|
||||||
<span class="tui__perm">-rw-r--r--</span><span class="tui__name">dossier.md</span>
|
|
||||||
</a></li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<figure class="tui__preview">
|
|
||||||
<div class="tui__text" data-text="info.md">{{ .Content }}</div>
|
|
||||||
{{ if $rapport }}<div class="tui__text" data-text="rapport.md" hidden>{{ $rapport.Content }}</div>{{ end }}
|
|
||||||
{{ if $dossier }}<div class="tui__text" data-text="dossier.md" hidden>{{ $dossier.Content }}</div>{{ end }}
|
|
||||||
<figcaption class="tui__status">info.md</figcaption>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<meta name="twitter:description" content="{{ $desc }}">
|
<meta name="twitter:description" content="{{ $desc }}">
|
||||||
{{ with $ogabs }}<meta name="twitter:image" content="{{ . }}">{{ end }}
|
{{ with $ogabs }}<meta name="twitter:image" content="{{ . }}">{{ end }}
|
||||||
|
|
||||||
<link rel="preload" as="font" type="font/woff2" href="{{ "fonts/iosevka-curly-slab-400.woff2" | relURL }}" crossorigin>
|
<link rel="preload" as="font" type="font/woff2" href="{{ "fonts/iosevka-400.woff2" | relURL }}" crossorigin>
|
||||||
|
|
||||||
{{ $site := resources.Get "css/site.css" }}
|
{{ $site := resources.Get "css/site.css" }}
|
||||||
{{ $custom := resources.Get "css/custom.css" }}
|
{{ $custom := resources.Get "css/custom.css" }}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
{{ $sorted := sort .Pages "Date" "desc" }}
|
{{ $sorted := sort .Pages "Date" "desc" }}
|
||||||
{{ $first := index $sorted 0 }}
|
{{ $first := index $sorted 0 }}
|
||||||
|
{{ $hasText := ne (.RawContent | strings.TrimSpace) "" }}
|
||||||
|
<div class="proj-layout">
|
||||||
<div class="tui filebrowser">
|
<div class="tui filebrowser">
|
||||||
<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>
|
<p class="tui__cmd"><span class="tui__sym">$</span> ls</p>
|
||||||
<p class="tui__cmd tui__total">total {{ len $sorted }}</p>
|
<p class="tui__cmd tui__total">total {{ len $sorted }}</p>
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
data-img="{{ with $p.Params.thumbnail }}{{ . | relURL }}{{ end }}"
|
data-img="{{ with $p.Params.thumbnail }}{{ . | relURL }}{{ end }}"
|
||||||
data-name="{{ $p.Slug }}"
|
data-name="{{ $p.Slug }}"
|
||||||
{{ if eq $i 0 }}class="is-selected"{{ end }}>
|
{{ if eq $i 0 }}class="is-selected"{{ end }}>
|
||||||
<span class="tui__perm">drwxr-xr-x</span><span class="tui__date">{{ $p.Date.Format "2006" }}</span><span class="tui__name">{{ $p.Slug }}/</span>
|
<span class="tui__perm">drwxr-xr-x</span><span class="tui__date">{{ $p.Date.Format "2006" }}</span><span class="tui__row3"><span class="tui__name">{{ $p.Slug }}/</span>{{ with $p.Params.categories }}<span class="tui__tags">{{ range . }}[{{ . }}]{{ end }}</span>{{ end }}</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -24,4 +26,6 @@
|
|||||||
<figcaption class="tui__status">{{ $first.Slug }}/ {{ $first.Date.Format "2006" }}</figcaption>
|
<figcaption class="tui__status">{{ $first.Slug }}/ {{ $first.Date.Format "2006" }}</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if $hasText }}<aside class="proj-info">{{ .Content }}</aside>{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -30,15 +30,16 @@
|
|||||||
<figure class="tui__preview">
|
<figure class="tui__preview">
|
||||||
{{ if $video }}
|
{{ if $video }}
|
||||||
<div class="player">
|
<div class="player">
|
||||||
<video preload="none" playsinline{{ with .Params.thumbnail }} poster="{{ . }}"{{ end }}>
|
<video preload="metadata" playsinline{{ with .Params.thumbnail }} poster="{{ . }}"{{ end }}>
|
||||||
<source src="{{ $video }}" type="video/mp4">
|
<source src="{{ $video }}" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
<button class="player__big" type="button" aria-label="play">▶</button>
|
<button class="player__big" type="button" aria-label="play">▶</button>
|
||||||
<div class="player__bar">
|
<div class="player__bar">
|
||||||
<button class="player__play" type="button" aria-label="play/pause">▶</button>
|
<button class="player__play" type="button" aria-label="play/pause"><span class="player__playicon">▶</span></button>
|
||||||
<div class="player__scrub"><div class="player__fill"></div></div>
|
<div class="player__scrub"><div class="player__fill"></div></div>
|
||||||
<span class="player__time">00:00 / 00:00</span>
|
<span class="player__time">00:00 / 00:00</span>
|
||||||
<button class="player__mute" type="button" aria-label="mute">vol</button>
|
<button class="player__mute" type="button" aria-label="mute">vol</button>
|
||||||
|
<div class="player__vol" aria-label="volume"><div class="player__volfill"></div></div>
|
||||||
<button class="player__full" type="button" aria-label="fullscreen">⛶</button>
|
<button class="player__full" type="button" aria-label="fullscreen">⛶</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 420 KiB |
|
After Width: | Height: | Size: 634 KiB |
|
After Width: | Height: | Size: 734 KiB |