/* ================= Forside ================= */
/*
  The hero fills the viewport from the nav to the footer, exactly like the
  Kontakt section (client request, 2026-08-26: "like on the contact page") —
  same A4 flex-fill mechanism, same reasoning, see the long block comment
  above `.page-kontakt main` further down. This replaced the old
  `min-height: var(--hero-min-height)` floor, and the hero-min-height theme
  setting went with it: a section sized by the viewport has no use for a
  configured pixel height. Reverted below 900px in responsive.css, where the
  columns stack and the media band gets its fixed 420px height back.
*/
.page-forside main {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.hero {
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  align-items: stretch;
  flex: 1;
  min-height: 0;
  /* Chrome is the sticky nav (63px) + the now-textless footer (53px:
     2 × --s26 padding + 1px border), measured live — see .kontakt's
     matching cap below, kept in step. */
  max-height: calc(100dvh - 116px);
}

.hero-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s26);
  padding: var(--s80) var(--page-x);
}

.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s14);
}
.hero-eyebrow-rule { height: 1px; width: 52px; background: var(--ink); }

.hero-name { font-family: var(--serif); }
/* Sizes come from the display1-size / display2-size theme settings
   (desktop only — see theme-vars.html.twig). responsive.css's own
   fixed-px overrides at <=1100/900/600 always win at those widths
   regardless of this var, by ordinary cascade order (later stylesheet,
   same selector specificity) — a deliberate limitation, not an oversight;
   see blueprints.yaml's help text on type_help. */
.hero-name .display-1 { display: block; font-size: var(--display1-size, 92px); }
.hero-name .display-2 { display: block; font-size: var(--display2-size, 82px); }

.hero-intro {
  max-width: 430px;
  font-size: var(--body-size, 16px);
  line-height: 1.65;
  color: var(--ink-soft);
}

/* Spacing from the intro comes from .hero-copy's own gap. The mockup nudges this
   row 6px further down with a margin, but the design system forbids margins
   between siblings, and 6px on a 26px gap is imperceptible. */
.hero-actions { display: flex; gap: var(--s14); }

.hero-media {
  position: relative;
  /* No min-height of its own: the column takes the row height the
     flex-filled .hero hands it (align-items: stretch above), the same way
     .kontakt-media does. min-height: 0 keeps the fill honest — the child
     image is absolutely positioned, so there is no content minimum to
     protect anyway. */
  min-height: 0;
  overflow: hidden;
  background: var(--rule);
}
.hero-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 60% 30%;
}

/* ================= Om mig ================= */
.om {
  padding: var(--page-y) var(--page-x);
  display: grid;
  grid-template-columns: 1.25fr 0.75fr;
  gap: var(--s70);
  align-items: start;
}


.om-main {
  display: flex;
  flex-direction: column;
  gap: var(--s22);
  max-width: 640px;
}

/* Column-and-gap now comes from the shared `.prose` role in base.css, which
   this container also carries — only the type stays page-specific. */
.om-body p { font-size: var(--body-size, 16px); line-height: 1.7; color: var(--ink-soft); }

.om-details {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s26) 40px;
}
/* Namespaced `om-` because Task 9's project page owns the bare `detail-` prefix
   for a different component. Same word, two meanings, one stylesheet. */
.om-detail { display: flex; flex-direction: column; gap: 3px; }
.om-detail-wide { grid-column: 1 / -1; }
.om-detail-value { font-size: 22px; color: var(--ink-soft); }
.om-detail-text { font-size: 15px; line-height: 1.6; color: var(--ink-soft); }

.om-side { display: flex; flex-direction: column; gap: var(--s14); }

/* Studiesang: an optional credits line sitting directly above the play tile
   (header.studiesang_credits, om.yaml). The pair gets its own container so
   the two are separated by s10 rather than .om-main's section-sized s22 gap
   — the line has to read as a caption belonging to the button beneath it,
   not as a stray sentence. align-items keeps the line flush with the tile's
   left edge, which is itself align-self: flex-start (layout.css). */
.studiesang {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s10);
}
/* .subhead in the markup supplies the italic serif and accent colour — same
   treatment as .uddannelse-institution just above, the closest existing
   precedent for a credit line. Sized below .tile-label's 18px so the tile
   stays the louder of the two. */
.studiesang-credits {
  font-size: 15px;
  line-height: 1.5;
  max-width: 520px;
}

.uddannelse {
  display: flex;
  flex-direction: column;
  gap: var(--s6);
  border-top: 1px solid var(--rule);
  padding-top: var(--s14);
}
/* .uddannelse-list carries the gap between entries so no sibling needs a
   margin — the outer .uddannelse gap (s6) separates the label from the
   list as a whole, and this inner gap (s10) separates entries within it. */
.uddannelse-list { display: flex; flex-direction: column; gap: var(--s10); }
.uddannelse-entry { display: flex; flex-direction: column; gap: 1px; }
.uddannelse-titel { font-size: 15px; font-weight: 600; }
.uddannelse-institution {
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  color: var(--accent);
}

/* ================= Projekter index ================= */
/* <ul> for list semantics -- ten projects, navigable as a list by a screen
   reader. The reset keeps it rendering exactly as the previous <div> did. */
.project-list { display: flex; flex-direction: column; gap: 38px; list-style: none; margin: 0; padding: 0; }

.project-row {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: var(--s44);
  align-items: center;
  padding-bottom: 38px;
  border-bottom: 1px solid var(--rule);
}

/* Outer gap (s16) separates the text block from the CTA; the inner
   .project-copy-text gap (s10) separates the text lines from each other.
   No margin needed on .link-rule — see the uddannelse-list precedent above
   for the same pattern. */
.project-copy { display: flex; flex-direction: column; gap: var(--s16); }
.project-copy-text { display: flex; flex-direction: column; gap: var(--s10); }
/* font-weight is explicit because this is an <h2>: base.css resets heading
   margins but not weight, and the UA default (bold) is not the design -- the
   element was a <span> inheriting body's 400 before the semantic fix. */
.project-title { font-family: var(--serif); font-size: 34px; line-height: 1.1; font-weight: 400; }
.project-sted { font-size: 18px; }
.project-resume {
  font-size: 15px;
  line-height: 1.7;
  color: var(--muted);
  max-width: 520px;
}

/* ================= Projekt detalje ================= */
.detail-hero { position: relative; height: 420px; overflow: hidden; }
.detail-hero-image { width: 100%; height: 420px; object-fit: cover; }

.detail-main {
  padding: var(--page-y) var(--page-x);
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: var(--s70);
  align-items: start;
}

.detail-copy { display: flex; flex-direction: column; gap: var(--s16); }
.detail-copy [data-back] { align-self: flex-start; }
.detail-title {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 52px;
  line-height: 1.05;
}
.detail-sted { font-size: 22px; }
/* Column-and-gap comes from the shared `.prose` role in base.css — see
   .om-body above. */
.detail-body { max-width: 620px; }
.detail-body p { font-size: var(--body-size, 16px); line-height: 1.75; color: var(--ink-soft); }

.detail-meta {
  display: flex;
  flex-direction: column;
  gap: var(--s14);
  border-top: 1px solid var(--rule);
  padding-top: var(--s16);
}
.detail-meta-item { display: flex; flex-direction: column; gap: var(--s2); }
.detail-meta-value { font-size: 15px; line-height: 1.6; }
/* A1: header.hold as a { navn, rolle } list — a name-and-role pair per
   line, matching Periode/Rolle's single-line rhythm rather than a wall of
   comma-separated text. gap, not margin, keeps the sibling spacing rule
   consistent with the rest of the theme. */
.detail-meta-hold { display: flex; flex-direction: column; gap: var(--s2); }
.hold-person { display: block; }

.photo-grid {
  padding: 0 var(--page-x) var(--s80);
  display: grid;
  /* photo-grid-columns theme setting, desktop only — responsive.css fixes
     this at 1 column below 600px regardless of the configured value. */
  grid-template-columns: repeat(var(--photo-grid-columns, 3), 1fr);
  gap: var(--s16);
}

.detail-video { padding: 0 var(--page-x) var(--s44); }
.detail-lyd { padding: 0 var(--page-x) var(--s44); }

/* Optional heading-and-prose intro above a video (projekt.yaml's
   .overskrift/.tekst per videoer row). Width and body type match
   .detail-body above so the intro reads as the same voice as the project
   text, not as a new component; the gap/padding pair keeps the s44 band
   rhythm .detail-video already has. */
.detail-video-intro {
  display: flex;
  flex-direction: column;
  gap: var(--s10);
  padding-bottom: var(--s16);
  max-width: 620px;
}
.detail-video-heading {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 28px;
  line-height: 1.15;
}
.detail-video-text p { font-size: var(--body-size, 16px); line-height: 1.75; color: var(--ink-soft); }

/* ================= Video og lyd ================= */
.video-player,
.video-frame,
.video-facade,
.audio-facade,
.audio-art {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  background: var(--ink-soft);
}

/* Fullscreen playback letterboxes against the element's own background —
   without this the bars around the film would show the theme's warm
   ink-soft brown instead of cinema black (client request, 2026-08-26).
   Only in fullscreen: in the page flow the tile keeps the theme tone while
   the poster loads. */
.video-player:fullscreen { background: #000; }
.video-player::backdrop { background: #000; }

.video-facade,
.audio-facade {
  position: relative;
  background-size: cover;
  background-position: center;
  cursor: pointer;
  padding: 0;
  font: inherit;
  /* "All images share the same desaturation filter" (DESIGN_GUIDELINES.md) —
     the showreel poster is a background-image, not an <img>, so it can't
     pick up .photo's filter; this is the same --photo-filter token applied
     directly. Compound selector for the override (not the bare
     .photo-grayscale class layout.css uses) because this file loads after
     layout.css — same specificity there would let source order flip the
     winner back to this rule; .video-facade.photo-grayscale always wins
     regardless of file order. */
  filter: var(--photo-filter);
}
.video-facade.photo-grayscale { filter: var(--photo-filter-grayscale-recipe); }
.video-facade::after,
.audio-facade::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(36, 23, 21, 0.45);
}
.video-facade-label,
.audio-facade-label {
  position: relative;
  z-index: 1;
  font-family: var(--serif);
  font-style: italic;
  font-size: 26px;
  /* Sits on the ::after scrim above, which is a fixed dark literal — so
     this must stay light in every palette (tokens.css --on-scrim), not
     follow --paper, which is dark in the "Mørk" preset. */
  color: var(--on-scrim);
}
.video-facade:hover::after,
.audio-facade:hover::after { background: rgba(36, 23, 21, 0.55); }

/* After the click (theme/js/audio.js): the poster survives as a static
   .audio-art band — same box, minus the scrim, label and cursor — with the
   native controls strip directly under it. A posterless facade (no Plakat
   chosen, or om's studiesang tile) is replaced by the bare player alone. */
.audio-art {
  background-size: cover;
  background-position: center;
  filter: var(--photo-filter);
}
.audio-player { display: block; width: 100%; }

/* ================= Galleri ================= */
.galleri { gap: var(--s34); }

.galleri-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  /* Was a hardcoded 30px. justify-content: space-between already pushes
     the heading and the showreel link apart on any realistic viewport —
     this gap is only a fallback minimum for narrow widths — so moving to
     the nearest scale step is not visually meaningful. */
  gap: var(--s26);
}

.galleri-grid {
  display: grid;
  /* gallery-columns theme setting, desktop only — responsive.css fixes
     this at 2 columns below 900px regardless of the configured value. */
  grid-template-columns: repeat(var(--gallery-columns, 4), 1fr);
  gap: var(--s16);
}

.galleri-bottom {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--s16);
  align-items: stretch;
}

.showreel { min-height: 340px; background: var(--ink); }
/* The showreel tile is fixed-height and dark. Whichever video.html.twig
   branch renders — click-to-load facade or native player — it must fill
   that height rather than falling back to its own aspect-ratio box.
   (The tile itself only renders when a reel exists — galleri.html.twig
   omits the whole band otherwise, so there is no empty state to size.) */
.showreel .video-facade,
.showreel .video-player,
.showreel .video-frame {
  height: 100%;
  min-height: 340px;
  aspect-ratio: auto;
}
/* Closing portrait slot — sized and positioned the same way .hero-media /
   .hero-image handle the Forside hero: a position:relative box the real
   image stretches to occupy. */
.galleri-slutbillede-slot {
  position: relative;
  min-height: 340px;
  overflow: hidden;
}
.galleri-slutbillede {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ================= Kontakt ================= */
/*
  A4: `main` becomes a column flex context — scoped to this route only via
  the page_class body class (base.html.twig / kontakt.html.twig), never a
  sitewide `main { display: flex }` — so `.kontakt` below can claim exactly
  the vertical space `main` has left after the sticky nav and the footer
  (base.css's `main { flex: var(--main-flex, 1); }`, the sticky-footer
  trick) via a real `flex: 1`, not a fixed vh calculation. A vh calculation
  would have to know the sticky nav's and the footer's exact pixel heights,
  and neither is a fixed number (a longer/shorter nav label, a taller
  footer at a narrower width, both already move the real value).

  A percentage height was tried first (`.kontakt { height: 100% }`, one
  level down from `main`) and does not work here: verified live in the
  browser that it resolves to `.kontakt`'s own content height, not main's,
  even though main's computed height is unambiguously definite (770px on a
  900px-tall viewport, confirmed via getBoundingClientRect) — a
  flex-grown ancestor's definite size does not reliably cascade through a
  CSS2.1 percentage on a non-flex descendant in the browsers this was
  tested in. A second, directly-nested flex item (`flex: 1`) reliably
  fills it instead, confirmed the same way, so that's what's below.
*/
.page-kontakt main {
  display: flex;
  flex-direction: column;
  /* The chain only holds if EVERY flex ancestor opts out of the default
     `min-height: auto`. body is min-height:100vh + flex column, main is
     flex:1 — but main's automatic minimum let it grow to its content
     (measured 1102px inside a 900px viewport), so .kontakt below inherited
     no real ceiling and the portrait sized itself. */
  min-height: 0;
}
.kontakt {
  /* No padding on the section: the media column is full-bleed like the
     Forside hero's, so the padding lives on .kontakt-copy instead. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s70);
  /* stretch, not start: the media column has no intrinsic height of its own
     (its only child is absolutely positioned, like .hero-image), so it must
     take the row's height or it collapses to zero. */
  align-items: stretch;
  /* See the block comment above `.page-kontakt main`. flex-grow (not a
     bare height) so a visitor with an unusually long intro paragraph still
     gets a taller section (and a scrolling page) rather than overlapping
     the footer — flex items never shrink below their own content's natural
     size by default (min-height: auto). See .kontakt-portrait below for
     the half of this that actually caps the image; both are reverted below
     900px in responsive.css — a viewport-height image on a phone would
     push the actual contact details off screen instead of fixing
     anything. */
  flex: 1;
  /* Without this, flex's default `min-height: auto` lets .kontakt grow to
     its tallest content (the portrait) instead of being sized by the space
     main has left — so `height: 100%` on the portrait resolved against a row
     the portrait itself defined, a circular constraint that capped nothing.
     Measured before: a 942px image in a 900px viewport. */
  min-height: 0;
  /* Caps the section — and therefore the media column the image fills — to
     the viewport so Kontakt needs no scrolling on desktop. Chrome is the
     sticky nav (63px) + the textless footer (53px: 2 × --s26 padding +
     1px border — was 68px while it still carried text). Kept in step with
     .hero's matching cap above. Reverted below 900px in responsive.css. */
  max-height: calc(100dvh - 116px);
}

.kontakt-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s26);
  padding: var(--page-y) var(--page-x);
}

/* Mirrors .hero-media: the image fills its grid column edge to edge rather
   than sitting inside the section's padding, with the rule tone showing
   through until the photo loads. */
.kontakt-media {
  position: relative;
  overflow: hidden;
  background: var(--rule);
}

.kontakt-intro {
  max-width: 420px;
  font-size: var(--body-size, 16px);
  line-height: 1.7;
  color: var(--ink-soft);
}

.kontakt-links { display: flex; flex-direction: column; gap: var(--s14); }
.kontakt-email {
  align-self: flex-start;
  /* Weight 700 to match .h1, not the serif's default 400 — the address is
     the page's primary call to action and was reading as body prose set
     large rather than as a heading. Sized just under the h1 above it so it
     sits in the same family without competing with the page title. */
  /* .subhead in the markup supplies the italic serif and accent colour;
     these are the client's specified weight and size. Note Playfair is
     loaded as a 400-900 variable font, so 300 renders at its 400 floor. */
  font-weight: 300;
  font-size: 31px;
  line-height: 1.2;
  border-bottom: 1px solid var(--rule);
  padding-bottom: var(--s10);
}
.kontakt-phone { align-self: flex-start; font-size: 26px; }

/* The CV download was using .link-rule — the 11px uppercase micro link used
   for "Se projekt →" and similar in-page nav. Downloading a CV is a real
   action a casting director comes here for, so it gets the outline button's
   presence and padding instead, one size up from the site's standard button. */
.kontakt-cv {
  align-self: flex-start;
  font-size: 13px;
  padding: var(--s16) var(--s34);
  margin-top: var(--s10);
}

.kontakt-meta { display: flex; flex-direction: column; gap: var(--s6); }
/* The profile link looks like a bare URL-shaped micro-label by design, but
   with nothing else marking it out, visitors couldn't tell it from the
   plain city text above it (client request, 2026-08-26) — underline it so
   it reads as clickable. text-underline-offset keeps the 11px uppercase
   letters clear of the line. */
.kontakt-meta [data-profile] {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Scraper obfuscation, the CSS half (see kontakt.html.twig's block comment
   and theme/js/contact.js): the DOM text arrives REVERSED from the server;
   this bidi override draws it right-to-left so the human-visible rendering
   is the real address even before — or without — JS. direction: rtl alone
   would also right-align the text inside the full-width flex item, so pin
   text-align back to the left edge the un-reversed text will occupy once
   contact.js swaps it (no visual jump). contact.js removes the class the
   moment it corrects the text. */
.obf {
  unicode-bidi: bidi-override;
  direction: rtl;
  text-align: left;
}

/* A4, continued from .kontakt's `flex: 1` above: fills that already-reserved
   box instead of the fixed `aspect-ratio: 3/4` every other `.photo-3-4`
   slot uses (layout.css). Deliberately placed after `.photo-3-4`
   (layout.css, loaded earlier — see partials/base.html.twig's stylesheet
   block) so it wins on source order at equal specificity without needing
   `!important` — this file's rules are the desktop default, responsive.css
   is what narrows them back down for smaller viewports, same convention
   every other breakpoint override in this theme follows. Applied via
   `classes: 'kontakt-portrait'` on the real image (kontakt.html.twig);
   with no portrait uploaded the whole media column is omitted, so there is
   no empty state to size. */
.kontakt-portrait {
  /* Same treatment as .hero-image on the Forside: absolutely fills its
     column, cropped by object-fit. The viewport cap that used to live here
     is now on .kontakt itself — capping the section caps the column, and
     the image simply fills whatever it gets. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
