:root{ --ink:#111; --muted:#555; --pheader-h:96px; }
*{box-sizing:border-box;}
html,body{height:100%;margin:0;overflow:hidden;}
body{font-family:'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;color:var(--ink);background:#fff;}
a{color:inherit;text-decoration:none;}

.pheader{position:fixed;top:0;left:0;right:0;z-index:6;padding:24px 20px 0;text-align:center;background:transparent;transition:color .3s ease;}
.pheader .logo-img{width:38vw;max-width:260px;height:auto;transition:filter .3s ease;filter:drop-shadow(0 1px 4px rgba(0,0,0,.55)) drop-shadow(0 0 10px rgba(0,0,0,.35));}
.pheader .header-social{display:none;}
.pheader nav.mainnav{margin-top:10px;display:flex;justify-content:center;gap:28px;}
.pheader nav.mainnav a{font-size:13px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#fff;opacity:.6;text-shadow:0 1px 4px rgba(0,0,0,.55), 0 0 10px rgba(0,0,0,.35);transition:color .3s ease,opacity .2s ease;}
.pheader nav.mainnav a.active{opacity:1;}
@media(min-width:768px){
  .pheader{display:flex;align-items:center;justify-content:space-between;padding:30px 40px;text-align:left;}
  .pheader .logo-img{width:320px;max-width:none;}
  .pheader .header-social{display:flex;gap:14px;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);}
  .pheader .header-social a{display:flex;align-items:center;justify-content:center;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.55), 0 0 10px rgba(0,0,0,.35);transition:color .3s ease;}
  .pheader nav.mainnav{margin-top:0;}
}

/* mix-blend-mode was tried here so the logo/nav/icons would stay legible
   against any video background, but .pheader and .viewport are separate
   fixed-position siblings (each creates its own stacking context) --
   verified live in Chrome that the blend never actually composites across
   that boundary: the white logo stayed flat white even over a bright sky,
   and over the white content view it disappeared outright instead of
   inverting to black. A drop-shadow/text-shadow halo (above) solves the
   same legibility problem without needing that cross-context blending: it
   keeps the logo/nav/icons readable over both dark and bright video
   frames, layered on top of the existing light/dark color toggle below for
   the content view. */
body.view-content .pheader .logo-img{filter:brightness(0);}
body.view-content .pheader nav.mainnav a,
body.view-content .pheader .header-social a{color:var(--ink);text-shadow:none;}

.viewport{position:fixed;inset:0;overflow:hidden;}
.track{position:relative;width:100%;height:calc(var(--vh100, 1vh) * 200);transition:transform .6s cubic-bezier(.65,0,.35,1);will-change:transform;}
.slide{position:relative;width:100%;height:calc(var(--vh100, 1vh) * 100);}

.slide-video{background-color:#000;background-size:cover;background-position:center;overflow:hidden;}
.slide-video iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.slide-video button.play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:none;border:0;padding:0;cursor:pointer;width:100%;z-index:4;}
.slide-video .scroll-capture{position:absolute;inset:0;z-index:3;background:transparent;}
/* Schliessen-Knopf fuer das Video im Kopf der Projektseite (Desktop). Liegt
   ueber der Scroll-Schicht, damit er immer anklickbar bleibt. */
/* Schliessen: das Kreuz als Zeichnung statt als Schriftzeichen. Ein &times;
   bringt seine eigenen Schriftmasse mit und sitzt darum nie ganz mittig im
   Kreis, egal was man an line-height dreht. */
.kopf-video-zu{position:absolute;top:18px;right:20px;z-index:6;width:40px;height:40px;
  border-radius:50%;border:1.5px solid rgba(255,255,255,.8);background:rgba(0,0,0,.32);
  color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;
  padding:0;line-height:0;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  transition:background .15s ease,border-color .15s ease;}
.kopf-video-zu svg{display:block;}

/* Die Schicht faengt das Mausrad ab, damit die Seite scrollt statt das
   eingebettete Fenster. Beim laufenden Video deckt sie nur noch die obere
   Haelfte: unten liegen die Bedienelemente von YouTube und Vimeo, und dort
   muss man hinkommen. Play, Pause und Vollbild macht der Player selbst. */
/* Die Symbole braucht es auf den Projektseiten nicht. */
.pheader .header-social{display:none !important;}

/* ---------- Buehne fuer eingebettete Videos (nur Desktop) ----------
   Beim Abspielen ist die Kopfzeile ganz weg und das Video fuellt den Schirm
   randlos. Beim Schliessen blendet alles wieder auf. */
@media(min-width:720px){
  .slide-video.video-buehne{background-color:#000;background-image:none !important;}

  .pheader{transition:opacity .4s ease,padding .5s cubic-bezier(.16,1,.3,1),color .3s ease;}
  /* :not(.view-content) ist wichtig: scrollt man waehrend dem Abspielen zur
     zweiten Ansicht, soll die Seite genau so aussehen wie immer.
     pointer-events:none ist ebenso wichtig -- die Kopfzeile ist ein Kind des
     body und liegt damit in einem anderen Stapelkontext als der Schliessen-
     Knopf im Video. Ohne diese Zeile faengt sie oben rechts dessen Klick ab. */
  /* Die Kopfzeile bleibt beim Abspielen sichtbar. Sie ist dabei als Ganzes
     klickdurchlaessig, damit sie oben rechts den Schliessen-Knopf nicht
     abfaengt; nur Logo und Menue nehmen selbst wieder Klicks an. */
  body.video-offen:not(.view-content) .pheader{opacity:1;pointer-events:none;z-index:40;}
  body.video-offen:not(.view-content) .pheader a{pointer-events:auto;}
  body.video-offen:not(.view-content) .slide-video.video-buehne .kopf-video-zu{z-index:30;}

  /* Randlos: das Fenster wird so gross gerechnet, dass es den Schirm in jedem
     Fall hineinpasst (das Kleinere von voller Breite und voller Hoehe im
     Format 16:9), und mittig gesetzt. Das Bild wird nie angeschnitten; bleibt
     Platz uebrig, steht er als schwarzer Rand daneben oder darueber und
     darunter. --vrand ist dieser Rand unten und rueckt die Fangschicht und
     den Pfeil entsprechend hoch. */
  .slide-video.video-buehne{--vrand:calc(50dvh - min(50dvh, 28.125vw));}
  .slide-video.video-buehne .kopf-video{position:absolute;top:50%;bottom:auto;left:50%;right:auto;
    transform:translate(-50%,-50%);margin:0;border:0;display:block;
    width:min(100vw, calc(100dvh * 16 / 9));
    height:min(100dvh, 56.25vw);
    min-width:0;min-height:0;}

  /* Die durchsichtige Schicht faengt das Mausrad ab (der Wechsel zur zweiten
     Ansicht haengt daran) und nimmt den Klick fuer Pause entgegen. Frei
     bleiben die untersten 120 Pixel: dort sitzt die Bedienleiste von YouTube
     mit Zeitleiste, Vollbild und dem Weg zu YouTube. Sie zeigt sich erst,
     wenn der Zeiger wirklich beim Player ist, darum lieber grosszuegig. */
  .slide-video.video-buehne .scroll-capture{top:0;bottom:calc(var(--vrand) + 120px);height:auto;z-index:5;
    cursor:pointer;}
  /* Der Pfeil bleibt beim Abspielen stehen. Er sitzt oberhalb der Bedienleiste
     von YouTube und ueber der Fangschicht, sonst kaeme sein Klick nicht an.
     Sein eigener Horcher stoppt das Weiterreichen, ein Klick darauf loest
     darum keine Pause aus. */
  .slide-video.video-buehne .scroll-hint{display:block;bottom:calc(var(--vrand) + 146px);z-index:6;
    opacity:.55;transition:opacity .2s ease;}
  .slide-video.video-buehne .scroll-hint:hover{opacity:1;}

  /* Der Schliessen-Knopf sitzt tiefer, damit er nicht auf den Knoepfen von
     YouTube oben rechts liegt, und dimmt bei Ruhe weg. */
  .slide-video.video-buehne .kopf-video-zu{z-index:9;right:64px;
    transition:opacity .3s ease,background .15s ease,border-color .15s ease;}
  .slide-video.video-buehne .kopf-video-zu.ruht{opacity:.22;}
  .slide-video.video-buehne .kopf-video-zu:hover{opacity:1;}
}




.kopf-video-zu:hover{background:rgba(0,0,0,.55);border-color:#fff;}
.play span{width:70px;height:70px;border:2px solid #fff;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:22px;}
.slide-video.launching button.play span{animation:playLaunch .42s ease forwards;}
@keyframes playLaunch{0%{transform:scale(1);opacity:1;}55%{transform:scale(1.3);opacity:.55;}100%{transform:scale(1);opacity:1;}}

.scroll-hint{position:absolute;left:50%;bottom:26px;z-index:5;color:#fff;opacity:.85;animation:scrollBounce 1.8s ease-in-out infinite;pointer-events:auto;cursor:pointer;padding:4px 16px;}
.scroll-hint svg{display:block;}
.scroll-hint-up{bottom:auto;top:26px;animation:scrollBounceUp 1.8s ease-in-out infinite;}
@keyframes scrollBounce{0%,100%{transform:translate(-50%,0);}50%{transform:translate(-50%,10px);}}
@keyframes scrollBounceUp{0%,100%{transform:translate(-50%,0);}50%{transform:translate(-50%,-10px);}}
.meta .scroll-hint,.extra-videos-page .scroll-hint,.stills-page .scroll-hint,.social-page .scroll-hint{color:var(--ink);opacity:.35;bottom:18px;}
.meta .scroll-hint-up,.extra-videos-page .scroll-hint-up,.stills-page .scroll-hint-up,.social-page .scroll-hint-up{bottom:auto;top:calc(var(--pheader-h) + 8px);}

.slide-content{background:#fff;overflow-y:auto;-webkit-overflow-scrolling:touch;}

.page-body{min-height:100%;box-sizing:border-box;padding-top:var(--pheader-h);display:flex;flex-direction:column;}

/* Fixed height so the gallery always begins at the identical vertical
   position on every project page, no matter how much title/credit/
   description text a given project has. The height itself is computed by
   JS (--meta-h, see project.js syncMetaHeight): header + gallery + footer
   are measured first, and .meta gets exactly what's left of the screen,
   so the content view keeps fitting in one screen instead of a guessed
   constant either overflowing or leaving a big empty gap. The value here
   is only the pre-JS fallback. Excess content (rare) becomes an internal
   scroll rather than ever shifting the gallery's start position. */
.meta{position:relative;max-width:none;margin:0;padding:0 20px;box-sizing:border-box;
  height:var(--meta-h, 50vh);overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;}
.meta-head{position:relative;z-index:1;}
.kicker{font-size:12px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:0 0 6px;}
.meta h1{font-size:clamp(48px,15vw,180px);line-height:0.92;margin:0;font-weight:800;max-width:95%;}
/* On phones the limiting factor is often vertical, not horizontal --
   long titles at a pure vw size can push the content view past one
   screen even with only 4 stills. Below 768px, also cap the title by
   available height (--vh100, falling back to vh) so it shrinks on
   short screens instead of forcing a scroll, while taller phones keep
   the normal vw-based size. */
@media(max-width:767.98px){
  .meta h1{font-size:clamp(32px, min(15vw, calc(var(--vh100, 1vh) * 7)), 96px);}
}
/* Desktop titles used a pure vw clamp up to 180px -- fine for a short
   title, but a longer one then wraps to several lines at that huge size
   and blows past any fixed .meta height. Capping the desktop max (and
   mixing in a height-based term, same idea as the mobile rule above)
   keeps title height in a comparable range across projects. */
@media(min-width:768px){
  .meta h1{font-size:calc(clamp(40px, min(8vw, calc(var(--vh100, 1vh) * 14)), 110px) * var(--fit, 1));}
}
.meta-top{display:flex;flex-wrap:wrap;gap:40px;}
.meta-left{flex:1 1 320px;min-width:0;}
.meta-body{position:relative;z-index:2;display:flex;flex-direction:column;align-items:flex-start;text-align:left;gap:10px;margin:36px 0 0;}
.meta-body p{font-size:14px;line-height:1.7;color:var(--muted);max-width:600px;margin:0;}
.credits{display:flex;flex-wrap:wrap;justify-content:flex-start;gap:48px;font-size:14px;color:var(--muted);}
/* Room on the right for one or more secondary video embeds, next to the
   left-aligned title/credits column -- opt-in per project via .meta-top /
   .meta-left / .extra-videos (see project-mastercard.html for the first
   use). Flex-wrap means it naturally drops to a single stacked column on
   narrow screens without a separate media query. */
.extra-videos{flex:1 1 280px;max-width:380px;display:flex;flex-direction:column;gap:20px;}

/* Zusatzvideos: einfaches Vorschaubild mit Play-Knopf. */
.extra-videos > .xvideo{position:relative;aspect-ratio:16/9;background:#111;cursor:pointer;overflow:hidden;
  border-radius:10px;box-shadow:0 10px 24px -14px rgba(0,0,0,.5);}
.extra-videos > .xvideo img{width:100%;height:100%;object-fit:cover;display:block;}
.extra-videos > .xvideo iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.xplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.25);}
.xplay span{width:44px;height:44px;border:2px solid #fff;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;}
.xtitle{margin-top:10px;font-size:12px;line-height:1.5;color:var(--muted);padding:0 2px;}

/* Social media posts (Instagram / TikTok). Markup: a .social-posts wrapper
   holding one .spost[data-url="<post link>"] per post. project.js turns each
   data-url into the platform's /embed iframe, and on mobile gives every post
   its own swipe page. */
/* Desktop: the posts sit in the right-hand column of .meta-top, next to the
   text -- same place Mastercard puts its extra videos. Three across, two rows.
   Each tile is cropped to the post's video area (project.js positions the
   iframe); the tile itself opens the post on Instagram, so nothing has to be
   playable at this size and the content slide never needs to scroll. */
.social-posts{flex:0 0 clamp(360px, 38vw, 540px);max-width:100%;align-self:start;display:grid;grid-template-columns:repeat(3,1fr);gap:16px;align-content:start;}
/* Two rows have to fit the height the content slide has left over for .meta,
   so the tile height comes straight from --meta-h (set by syncMetaHeight in
   project.js) rather than from an aspect ratio. */
/* Eine Reihe mit hoechstens drei Beitraegen, dafuer ueber die ganze Hoehe, die
   unter dem Titel uebrig bleibt. Vorher waren es zwei Reihen zu dritt, bei sechs
   Beitraegen wurde jede Kachel viel zu klein. */
/* Die Beitraege sehen aus wie ein iPhone im Hochformat: 9:16, runde Ecken,
   Gehaeuserand, Dynamic Island, leicht in den Raum gedreht. Das Video fuellt die
   Flaeche randlos (siehe layoutSocialPosts). Die 30px unten lassen Platz fuer
   Gehaeuse und Schatten, sonst wird das Handy am unteren Rand abgeschnitten. */
.spost{position:relative;width:100%;aspect-ratio:9/16;height:auto;justify-self:center;
  max-width:calc((var(--meta-h, 360px) - var(--kopf-h, 0px) - 30px) * 0.5625);
  overflow:hidden;background:#000;cursor:pointer;border-radius:12.5%/7%;
  box-shadow:
    0 0 0 2px #3a3d42,
    0 0 0 6px #0c0d0f,
    0 0 0 7px rgba(255,255,255,.14),
    0 22px 34px -16px rgba(17,18,20,.6),
    0 6px 14px -6px rgba(17,18,20,.4);
  transform:perspective(1100px) rotateY(-8deg) rotateX(2deg);
  transform-origin:50% 60%;
  transition:transform .55s cubic-bezier(.22,.61,.36,1), box-shadow .55s ease;}
.spost:hover{transform:perspective(1100px) rotateY(0deg) rotateX(0deg) translateY(-4px);
  box-shadow:
    0 0 0 2px #3a3d42,
    0 0 0 6px #0c0d0f,
    0 0 0 7px rgba(255,255,255,.18),
    0 30px 44px -18px rgba(17,18,20,.6),
    0 8px 18px -8px rgba(17,18,20,.45);}
@media (prefers-reduced-motion:reduce){ .spost,.spost:hover{transform:none;transition:none;} }
/* Immer bedienbar, sonst laesst sich das Video nicht starten. */
.spost iframe{position:absolute;left:0;top:0;width:100%;height:100%;border:0;display:block;pointer-events:auto;}
.spost .sbadge{position:absolute;right:8px;bottom:8px;z-index:3;width:18px;height:18px;opacity:.95;color:#fff;filter:drop-shadow(0 1px 3px rgba(0,0,0,.6));pointer-events:none;}
/* Dynamic Island und ein feiner Glanz auf dem Glas. */
.spost::before{content:'';position:absolute;top:3.5%;left:50%;transform:translateX(-50%);
  width:30%;height:5.5%;min-height:9px;border-radius:999px;background:#08090a;z-index:3;pointer-events:none;}
.spost::after{content:'';position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:inherit;
  background:linear-gradient(115deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,.05) 22%,rgba(255,255,255,0) 46%);}
.spost::after{transition:opacity .3s ease;}
.spost:hover::after{opacity:.5;}
.credits .group div{line-height:1.7;}
@media(min-width:768px){
  .meta{padding:0 40px;}

  /* Content pages are split down the middle: everything textual lives in the
     left half, extra videos or social posts fill the right half. The split
     only kicks in when there actually is something for the right side --
     text-only projects keep the full width. */
  .meta-top{display:grid;grid-template-columns:1fr;gap:40px;align-items:start;}
  .meta-top:has(.extra-videos),.meta-top:has(.social-posts),.meta-top:has(.meta-side){grid-template-columns:1fr 1fr;}
  .meta-left{min-width:0;}
  .extra-videos,.social-posts,.meta-side{min-width:0;max-width:none;}

  /* Ohne Videos und ohne Social-Beitraege stehen die Credits in der zweiten
     Haelfte, untereinander statt nebeneinander. */
  .meta-side{margin-top:var(--kopf-h, 0px);}
  .meta-side .credits{flex-direction:column;gap:calc(22px * var(--fit, 1));}

  /* Stehen Text und Credits untereinander in derselben Spalte, trennt sie ein
     duenner Strich auf halber Breite -- sonst liest sich das wie ein Block. */
  /* Nur wenn wirklich ein Beschrieb darueber steht -- sonst haengt der Strich
     ohne Grund unter dem Titel. */
  .meta-left .meta-body p ~ .credits{margin-top:calc(30px * var(--fit, 1));padding-top:calc(26px * var(--fit, 1));
    border-top:1px solid rgba(17,18,20,.16);width:50%;min-width:260px;}

  /* In the split layout the title only has half the width, so a headline set
     for the full width would eat the whole half on its own and force fitMeta()
     to shrink the credits along with it. A smaller cap here keeps the credits
     readable. */
  .meta-top:has(.extra-videos) h1,.meta-top:has(.social-posts) h1,.meta-top:has(.meta-side) h1{font-size:calc(clamp(34px, min(5.5vw, calc(var(--vh100, 1vh) * 10)), 76px) * var(--fit, 1));}
  .meta-top:has(.extra-videos) .credits,.meta-top:has(.social-posts) .credits{gap:calc(30px * var(--fit, 1));}

  /* Extra videos lay themselves out across the half instead of stacking in a
     narrow column, so two of them fit side by side and stay inside the height
     the content slide has to give. */
  .extra-videos{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;align-content:start;}

  /* Die rechte Spalte beginnt erst unter dem Titel. --kopf-h misst project.js
     (kopfhoeheMerken) aus der tatsaechlichen Hoehe von .meta-head. */
  .extra-videos,.social-posts{margin-top:var(--kopf-h, 0px);}
  .social-posts{grid-template-columns:repeat(3,1fr);}

  /* --fit is set by fitMeta() in project.js: if the text still does not fit the
     height left over for .meta, everything textual is scaled down a notch at a
     time until it does. That keeps the promise that a content page is exactly
     one screen, without an inner scrollbar. */
  .meta-body{margin-top:calc(40px * var(--fit, 1));}
  .meta-body p{font-size:calc(14px * var(--fit, 1));}
  .credits{gap:calc(48px * var(--fit, 1));font-size:calc(14px * var(--fit, 1));}
}

/* Gallery: horizontal-scroll carousel instead of a static grid, so a
   project can show more than 4 stills -- swipe/scroll or use the arrow
   buttons to reveal the rest. Images stay clickable to open the lightbox. */
/* <picture> darf im Raster und im Flexlauf nicht als eigener Kasten zaehlen.
   display:contents macht das Bild wieder zum direkten Kind. */
picture{display:contents}
/* Mit display:contents auf <picture> rutscht auch <source> ins Raster und zaehlt
   als eigenes Feld -- daraus wurden aus 4 Zeilen 8. Darum ausdruecklich weg. */
picture source{display:none}
.gallery-wrap{position:relative;margin-top:28px;}
.gallery{display:flex;gap:2px;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
.gallery::-webkit-scrollbar{display:none;}
/* height:auto ist Pflicht, seit die Bilder width und height als Attribut tragen.
   Ohne das nimmt der Browser die Originalhoehe und das Seitenverhaeltnis wird ignoriert. */
.gallery img{flex:0 0 auto;width:82%;height:auto;aspect-ratio:16/9;object-fit:cover;display:block;scroll-snap-align:start;cursor:pointer;}
.gal-arrow{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;border-radius:50%;border:0;background:rgba(17,17,17,.55);color:#fff;font-size:20px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2;}
.gal-arrow:hover{background:rgba(17,17,17,.8);}
.gal-prev{left:8px;}
.gal-next{right:8px;}
@media(min-width:720px){
  .gallery img{width:calc(25% - 1.5px);}
}

footer{padding:40px 20px;text-align:center;font-size:12px;color:var(--muted);margin-top:auto;}
.foot-social{display:flex;justify-content:center;gap:14px;margin-bottom:14px;}
.foot-social a{display:flex;align-items:center;justify-content:center;color:var(--ink);}
.foot-logo{width:120px;height:auto;filter:brightness(0);}

/* Lightbox: fullscreen still viewer with close + prev/next, cycling
   through every still in the project's gallery. */
.lightbox{position:fixed;inset:0;background:rgba(0,0,0,.95);z-index:50;display:none;align-items:center;justify-content:center;}
.lightbox.open{display:flex;}
.lightbox .lb-img{max-width:90vw;max-height:80vh;object-fit:contain;display:block;}
.lightbox .lb-close,.lightbox .lb-prev,.lightbox .lb-next{position:absolute;background:none;border:0;color:#fff;cursor:pointer;line-height:1;}
.lightbox .lb-close{top:18px;right:20px;font-size:34px;}
.lightbox .lb-prev,.lightbox .lb-next{top:50%;transform:translateY(-50%);font-size:48px;padding:10px 16px;}
.lightbox .lb-prev{left:8px;}
.lightbox .lb-next{right:8px;}
@media(min-width:768px){ .lightbox .lb-close{font-size:40px;} }


/* Mobile only: replace the hijacked wheel/touch 2-view state machine
   (video <-> content) with plain full-height pages the browser
   scroll-snaps between natively: video (with play button), then
   title+credits, then (if the project has them) extra videos, then
   (if the project has them) stills -- with the footer always attached
   to whichever of those content pages is last, mirroring the Work
   overview's mobile paging. Collapsing .viewport/.track/.slide-content/
   .page-body with display:contents removes their boxes (and the
   fixed/transformed positioning that drove the desktop hijack) while
   keeping their children -- .slide-video, .meta, .extra-videos-page,
   .stills-page (the latter two built by project.js, setupMobilePaging)
   -- as if they were direct children of <body>, which is what CSS
   scroll-snap requires for them to align. Desktop/tablet (>=720px) are
   completely untouched by this block. */
@media(max-width:719.98px){
  html{height:100%;overflow:hidden;}
  body{height:100vh;height:100dvh;overflow-y:auto;overflow-x:hidden;scroll-snap-type:y mandatory;-webkit-overflow-scrolling:touch;}
  .viewport,.track,.slide-content,.page-body{display:contents;}
  .slide-video{scroll-snap-align:start;scroll-snap-stop:always;height:100vh;height:100dvh;}

  /* Every page after the video is a fixed one-screen-tall snap section
     with its own internal scroll as a safety net, instead of growing
     taller than one screen and letting the next section's content peek
     into view at the bottom of the swipe (the previous bug). */
  /* Keine inneren Scrollbereiche: ein Abschnitt ist immer genau ein Wisch.
     Passt der Inhalt nicht, teilt project.js ihn auf und verkleinert notfalls
     ueber --mfit (siehe mobileSeitenFuellen). */
  .meta,.extra-videos-page,.stills-page,.social-page{scroll-snap-align:start;scroll-snap-stop:always;height:100vh;height:100dvh;overflow:hidden;overscroll-behavior-y:contain;box-sizing:border-box;position:relative;display:flex;flex-direction:column;justify-content:flex-start;}
  .meta{padding-top:calc(var(--pheader-h) + 46px);}
  .extra-videos-page{padding:calc(var(--pheader-h) + 26px) 20px 46px;justify-content:center;}
  .stills-page{padding-top:calc(var(--pheader-h) + 20px);padding-bottom:20px;}
  .social-page{padding:calc(var(--pheader-h) + 46px) 14px 56px;align-items:center;}
  /* Auf dem Handy dieselbe Zuschnittrechnung wie am Desktop, aber ohne
     Gehaeuse -- das Geraet ist ja selber das Handy. Die Breite haengt an der
     verfuegbaren Hoehe, damit nichts unten abgeschnitten wird. */
  .social-page .spost{flex:0 0 auto;align-self:center;min-height:0;
    width:min(100%, calc((100dvh - var(--pheader-h) - 120px) * 0.5625));
    max-width:420px;aspect-ratio:9/16;height:auto;
    background:#000;cursor:pointer;border-radius:14px;overflow:hidden;transform:none;
    box-shadow:0 10px 26px -14px rgba(17,18,20,.5);}
  .social-page .spost::before{display:none;}
  .social-page .spost::after{background:none;}
  .social-page .spost iframe{pointer-events:auto;}
  /* Muss absolut bleiben, sonst greift die Zuschnittrechnung nicht und es
     bleiben schwarze Balken stehen. */
  .social-page .spost iframe{position:absolute;pointer-events:auto;}
  .social-page .spost .sbadge{display:none;}
  .meta footer,.extra-videos-page footer,.social-page footer{margin-top:auto;}
  .stills-page footer{margin-top:24px;}

  /* Mobile keeps the same left-aligned layout as desktop, just stacked
     into a single column (title, then credits) instead of the desktop's
     two-column layout. Extra videos (if any) move to their own swipe
     page (see project.js setupMobilePaging), so this page only ever
     shows the title and credits. */
  .meta-top{flex-direction:column;align-items:flex-start;}

  /* --mfit ist der Verkleinerungsfaktor, den project.js setzt, wenn eine Seite
     sonst ueberlaufen wuerde. Vorgabe 1, also unveraendert. */
  .meta h1{font-size:calc(clamp(32px, min(15vw, calc(var(--vh100, 1vh) * 7)), 96px) * var(--mfit, 1));}
  .kicker{font-size:calc(12px * var(--mfit, 1));}
  .meta-body{margin-top:calc(36px * var(--mfit, 1));gap:calc(10px * var(--mfit, 1));}
  .meta-body p{font-size:calc(14px * var(--mfit, 1));}
  .credits{gap:calc(26px * var(--mfit, 1));font-size:calc(14px * var(--mfit, 1));flex-direction:column;}
  .credits .group{display:flex;flex-direction:column;gap:calc(3px * var(--mfit, 1));}

  /* Fortsetzungsseiten und die Credits-Seite tragen selber die Klasse .meta,
     bekommen also Polsterung, Pfeile und Wischverhalten geschenkt. */
  .text-page .meta-body,.credits-page .credits{margin-top:0;}
  .meta-left .meta-body p ~ .credits,.credits-page .credits{border-top:0;padding-top:0;width:auto;min-width:0;}
  .credits-page{justify-content:flex-start;}
  /* flex:0 0 auto ist wichtig: sonst streckt sich der Kasten auf die ganze
     Seitenhoehe und zuVoll() meldet dauernd Ueberlauf. Die Zusatzvideos folgen
     --mfit, damit das Verkleinern hier wirklich wirkt -- die Bildgroesse haengt
     an der Breite, nicht an der Schriftgroesse. */
  .extra-videos{flex:0 0 auto;width:100%;max-width:calc(340px * var(--mfit, 1));margin:0 auto;}

  /* Gallery on mobile: instead of one still per swipe, lay images out in
     a 2-column grid and page through groups of them -- as many as fit
     the screen -- via the same horizontal swipe/scroll-snap the carousel
     already used. Grouping into .gallery-page happens in project.js
     (setupMobileGalleryGrid), based on the actual available height. */
  .gallery-page{flex:0 0 100%;width:100%;scroll-snap-align:start;display:grid;grid-template-columns:repeat(2,1fr);gap:2px;align-content:start;}
  .gallery-page img{width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;display:block;}
  .gal-arrow{display:none;}
}

/* Tastaturbedienung: ohne sichtbaren Fokus weiss man nicht, wo man gerade steht.
   :focus-visible trifft nur die Tastatur, ein Mausklick loest nichts aus. */
a:focus-visible,button:focus-visible,[tabindex]:focus-visible,summary:focus-visible,
input:focus-visible,textarea:focus-visible,select:focus-visible{
  outline:2px solid currentColor;outline-offset:3px;border-radius:3px;}
/* Auf dunklem Grund braucht es Weiss, currentColor waere dort unsichtbar. */
.slide-video a:focus-visible,.slide-video button:focus-visible,
.tile:focus-visible,.kachel:focus-visible,.sitebar a:focus-visible,
.sitefoot a:focus-visible,.spost:focus-visible,.xvideo:focus-visible{
  outline-color:#fff;outline-offset:2px;}
/* Sprunglink: unsichtbar, bis er den Fokus bekommt. */
.sprung{position:absolute;left:-9999px;top:0;z-index:99;background:#111214;color:#fff;
  padding:10px 16px;font-size:13px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  text-decoration:none;border-radius:0 0 6px 0;}
.sprung:focus{left:0;}

/* Vollbildschicht fuer das Hauptvideo auf dem Handy. Liegt ausserhalb des
   Wischbereichs, darum stoert das Scroll-Snapping den Spieler nicht. */
.vollbild{position:fixed;inset:0;z-index:60;background:#000;display:flex;
  align-items:center;justify-content:center;touch-action:none;
  animation:vollbildRein .22s ease;}
@keyframes vollbildRein{from{opacity:0;}to{opacity:1;}}
/* Das YouTube-Fenster bekommt sein 16:9, sonst steht der Spieler als schmaler
   Streifen in einer riesigen schwarzen Flaeche. Eigene Videos fuellen die Hoehe. */
.vollbild iframe{width:100%;height:auto;aspect-ratio:16/9;max-height:100%;border:0;display:block;
  background:#000;touch-action:auto;}
.vollbild video{width:100%;height:100%;border:0;display:block;background:#000;
  object-fit:contain;touch-action:auto;}
.vollbild-zu{position:absolute;z-index:2;
  top:calc(env(safe-area-inset-top, 0px) + 12px);right:12px;
  width:42px;height:42px;border-radius:50%;border:0;cursor:pointer;
  background:rgba(0,0,0,.55);color:#fff;font-size:26px;line-height:1;
  display:flex;align-items:center;justify-content:center;
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);}
.vollbild-zu:hover{background:rgba(0,0,0,.75);}
@media (prefers-reduced-motion:reduce){ .vollbild{animation:none;} }
/* Die Credits bekommen auf dem Handy eine eigene Wischseite, sobald es einen
   Beschrieb gibt. Sie stehen dort allein und beginnen oben, wie der Text auf
   den anderen Seiten auch. */
@media(max-width:719.98px){
  .text-page.credits-page{display:flex;flex-direction:column;justify-content:flex-start;}
  .text-page.credits-page .meta-side{margin:0;}
}