@charset "UTF-8";

/* ===========================================================================
   galleri.css — kategoriseksjoner og masonry-rutenettet
   Fotografportefølje for David · Kristensen IT

   Forutsetter css/stil.css: tokens, reset og typografi kommer derfra, og den
   må lastes først. Kategorimenyen og lightboxen hører også hjemme i denne
   fila, men er ikke bygget ennå.
   Se docs/galleri.md for reglene bak rutenettet.
   =========================================================================== */


/* ---------------------------------------------------------------------------
   1. Siden
   --------------------------------------------------------------------------- */

.galleri {
  max-width: var(--maks-bredde);
  margin-inline: auto;
  padding: var(--rom-16) var(--side-marg) var(--rom-24);
}

.galleri__status,
.galleri__noskript {
  margin-top: var(--rom-8);
}

.galleri__kategorier {
  margin-top: var(--rom-12);
}


/* ---------------------------------------------------------------------------
   2. Kategoriseksjonene
   --------------------------------------------------------------------------- */

/* Luften mellom kategoriene er det eneste som skiller dem — ingen linjer,
   ingen bakgrunnsflater. Den må derfor være raus nok til å lese som et skille. */
.galleri__seksjon + .galleri__seksjon {
  margin-top: var(--rom-24);
}

/* Hvor et ankerhopp lander. --nav-hoyde settes av js/navigasjon.js med
   ResizeObserver, siden menyhøyden endrer seg mellom mobil og desktop.
   Reserven på 0px gjelder bare øyeblikket før menyen finnes.

   Dette er ETT sted, med vilje: js/navigasjon.js leser denne verdien tilbake
   med getComputedStyle framfor å regne ut sin egen. Da kan ikke den myke
   scrollingen og nettleserens egne ankerhopp havne i utakt. */
.galleri__seksjon {
  scroll-margin-top: calc(var(--nav-hoyde, 0px) + var(--rom-8));
}

.galleri__beskrivelse {
  max-width: var(--maks-tekst);
  margin-top: var(--rom-3);
}


/* ---------------------------------------------------------------------------
   3. Masonry-rutenettet

   Behovet: stående, liggende og kvadratiske bilder side om side, tett pakket,
   uten hull og uten at noe beskjæres.

   Løsningen er et vanlig CSS Grid med svært lave rader, der js/galleri.js
   regner ut hvor mange rader hvert bilde skal spenne over. Utregningen bruker
   sideforholdet fra manifestet, ikke de lastede bildene, så layouten sitter
   riktig fra første maling.

   Endres kolonnetallene her, må STORRELSER i js/galleri.js endres i samme
   runde — den forteller nettleseren hvor bred en kolonne blir.
   --------------------------------------------------------------------------- */

.galleri__rutenett {
  /* Disse tre leses av js/galleri.js med getComputedStyle. Endrer du navnene
     her, må de endres der. */
  --galleri-kolonner: 1;
  --galleri-mellomrom: var(--rom-6);

  /* Bevisst px og ikke rem: verdien regnes sammen med pikselbredder i
     JavaScript, og skal ikke flytte seg om brukeren endrer skriftstørrelsen.
     8px gir under 8px overflødig luft under et bilde — avrundingen av
     kolonnebredden legger til en brøkdel, målt til 8,2px som verste tilfelle
     — usynlig, uten å lage tusenvis av rader per seksjon. */
  --rad-enhet: 8px;

  margin-top: var(--rom-8);

  display: grid;
  grid-template-columns: repeat(var(--galleri-kolonner), 1fr);

  /* Ikke gap-stenografien: row-gap MÅ være 0. Med row-gap satt koster hvert
     radskille radEnhet + gap, så den effektive radoppløsningen blir 40px i
     stedet for 8px, og pakkingen blir grovkornet. Luften mellom bildene bæres
     av radspennet i js/galleri.js, som legger --galleri-mellomrom inn i
     høyden det ber om. */
  column-gap: var(--galleri-mellomrom);
  row-gap: 0;
  grid-auto-rows: var(--rad-enhet);

  /* dense er valgt bevisst. Uten den plasserer grid bildene strengt i
     DOM-rekkefølge, og et høyt bilde som blokkerer en kolonne etterlater
     store hull: markøren i auto-plasseringen flytter seg aldri bakover, så
     plass som frigjøres lenger opp blir stående ubrukt. Kostnaden er at
     visuell rekkefølge kan avvike fra rekkefolge-feltet i manifestet — det er
     akseptert, fordi tett pakking betyr mer i et galleri enn streng
     rekkefølge. Se docs/galleri.md. */
  grid-auto-flow: dense;
}

@media (min-width: 600px) {
  .galleri__rutenett { --galleri-kolonner: 2; }
}

@media (min-width: 1024px) {
  .galleri__rutenett { --galleri-kolonner: 3; }
}

@media (min-width: 1440px) {
  .galleri__rutenett {
    --galleri-kolonner: 4;
    --galleri-mellomrom: var(--rom-8);
  }
}


/* ---------------------------------------------------------------------------
   4. Bildene

   Bildeforholdet er hellig (docs/galleri.md). Her står det derfor ingen fast
   høyde, ingen aspect-ratio og ingen object-fit i det hele tatt. Bredden
   fyller kolonnen, høyden følger av bildet selv, og radspennet er regnet ut
   for å gi plass til akkurat den høyden.

   Legger noen på object-fit: cover her, beskjæres kundens bilder. Det er
   nettopp det han har bedt oss om å ikke gjøre.
   --------------------------------------------------------------------------- */

.galleri__figur img {
  display: block;
  width: 100%;
  height: auto;
}


/* ---------------------------------------------------------------------------
   5. Reservelayout

   js/galleri.js setter data-layout="rutenett" først når radspennene er på
   plass. Uten det attributtet ville grid-oppsettet gitt hvert bilde én rad på
   8px, og bildene ville ligget oppå hverandre. Fram til da — og hvis
   JavaScript aldri kommer så langt — pakkes bildene i flerspaltet flyt i
   stedet. Det gir ikke like tett pakking, men ingenting overlapper og
   sideforholdet er like urørt.

   Manifestet krever fetch, så helt uten JavaScript er galleriet tomt uansett.
   Reservelayouten dekker øyeblikket mellom bygging og utregning, og dagen vi
   eventuelt prerendrer HTML.
   --------------------------------------------------------------------------- */

.galleri__rutenett:not([data-layout="rutenett"]) {
  display: block;
  columns: var(--galleri-kolonner);
  column-gap: var(--galleri-mellomrom);
}

.galleri__rutenett:not([data-layout="rutenett"]) .galleri__figur {
  break-inside: avoid;
  margin-bottom: var(--galleri-mellomrom);
}


/* ---------------------------------------------------------------------------
   6. Kategorimenyen

   Bygges av js/navigasjon.js fra de samme kategoriene som seksjonene. Finnes
   ikke i HTML, og finnes ikke uten JavaScript — men galleriet gjør heller
   ikke det, så menyen er ikke det som gjør siden avhengig av JS.
   --------------------------------------------------------------------------- */

.kategorimeny {
  position: sticky;
  top: 0;
  z-index: 10;

  /* sticky er en posisjonert boks, så indikatoren under kan absolutt-
     posisjoneres mot denne uten en egen position: relative. */
  margin-top: var(--rom-8);
  border-bottom: 1px solid var(--hårstrek);

  /* Ugjennomsiktig fallback først. Uten backdrop-filter må stripa være tett,
     ellers blir menyteksten uleselig idet et bilde glir under den. */
  background: var(--papir);
}

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .kategorimeny {
    background: var(--slør);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }
}

.kategorimeny__liste {
  display: flex;
  gap: var(--rom-6);
  list-style: none;
  padding: 0;

  /* Vannrett scrolling på smale skjermer. Stripa skal ikke vise rullefelt —
     den er kort nok til at kantene røper at det er mer å se. */
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}

.kategorimeny__liste::-webkit-scrollbar {
  display: none;
}

.kategorimeny__lenke {
  display: block;
  padding: var(--rom-4) 0;

  color: var(--blekk-dempet);
  text-decoration: none;
  white-space: nowrap;
  scroll-snap-align: center;

  transition: color var(--tid-rask) var(--kurve);
}

.kategorimeny__lenke[aria-current="true"] {
  color: var(--blekk);
  font-weight: 500;
}

.kategorimeny__indikator {
  position: absolute;
  left: 0;
  bottom: -1px;        /* legger seg oppå hårstreken, ikke under den */

  width: 0;
  height: 1px;
  background: var(--blekk);

  /* Bare transform og width. Animerer man left eller right i stedet, tvinges
     reflow hver eneste frame. */
  transition: transform var(--tid-rolig) var(--kurve),
              width var(--tid-rolig) var(--kurve);
  pointer-events: none;
}


/* ---------------------------------------------------------------------------
   8. Lightbox

   js/lightbox.js bygger dialogen første gang noen åpner et bilde, og pakker
   hvert bilde i galleriet i en <button> når modulen kjører. Uten JavaScript
   finnes ingen av delene, og bildene er bare bilder — derfor står det ingen
   lightbox-markup i galleri.html.

   Bildeforholdet er hellig også her: object-fit er contain, aldri cover, og
   bildet har ingen fast høyde.
   --------------------------------------------------------------------------- */

/* Utløseren skal ikke se ut som en knapp. Den er bare en flate rundt bildet
   som tar imot klikk, Enter og mellomrom, og som kan få fokus. */
.galleri__utloser {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

.galleri__utloser:focus-visible {
  outline: 2px solid var(--blekk);
  outline-offset: 3px;
}

.lightbox {
  /* <dialog> kommer med ramme, polstring, bakgrunn og automatiske marger.
     Alt sammen må vekk før den kan dekke skjermen. */
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;   /* ::backdrop bærer flaten */
  overflow: hidden;
}

/* Nær sort, ikke helt: en ren #000 gjør at mørke partier i bildet renner ut i
   bakgrunnen og mister kanten sin. Fargen er --blekk med gjennomsikt. */
.lightbox::backdrop {
  background: rgba(17, 17, 17, 0.94);
}

.lightbox__figur {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  gap: var(--rom-4);
  place-items: center;

  width: 100%;
  height: 100%;
  margin: 0;
  padding: var(--rom-12) var(--side-marg) var(--rom-6);
}

.lightbox__bilde {
  display: block;

  /* Bildet får aldri bli større enn cellen sin, og skalerer proporsjonalt ned
     til det passer. Ingen fast høyde, ingen aspect-ratio. */
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;

  /* Boksen har allerede bildets eget forhold, så denne gjør i praksis
     ingenting. Den står som en sperre: skulle noen senere gi bildet en fast
     høyde, beskjærer contain likevel ikke. */
  object-fit: contain;
}

/* Bildeteksten står på mørk flate, der --blekk-dempet ville vært uleselig.
   --blekk-svak brukt som lys grå gir samme dempede rolle motsatt vei. */
.lightbox__tekst {
  margin: 0;
  color: var(--blekk-svak);
  font-size: var(--skala-liten);
  text-align: center;
  text-wrap: balance;
}

.lightbox__tekst[hidden] {
  display: none;
}

.lightbox__lukk {
  position: absolute;
  top: var(--rom-4);
  right: var(--rom-4);
  z-index: 1;

  display: grid;
  place-items: center;
  width: var(--rom-8);
  height: var(--rom-8);
  padding: 0;

  border: 0;
  background: none;
  color: var(--blekk-svak);
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;

  transition: color var(--tid-rask) var(--kurve);
}

.lightbox__lukk:hover {
  color: var(--papir);
}

/* Fokusringen ellers på nettstedet er --blekk, som forsvinner mot denne
   flaten. Her snus den. */
.lightbox__lukk:focus-visible {
  outline: 2px solid var(--papir);
  outline-offset: 2px;
  color: var(--papir);
}

/* Innfading. Bildet skal komme fram, ikke sprette. Bare opasitet animeres —
   ingenting som tvinger layout. */
@media (prefers-reduced-motion: no-preference) {
  .lightbox[open],
  .lightbox[open]::backdrop {
    animation: lightbox-inn var(--tid-rolig) var(--kurve);
  }
}

@keyframes lightbox-inn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
