/* ==========================================================================
   Cemen Bakin — portfolio

   Five rules this stylesheet keeps:
   1. The page has no accent colour. Every colour you see belongs to the work
      being shown. Emphasis is contrast, an underline, or a dot — never a hue.
   2. Dark is the ground the page was designed on; light is a proper
      inversion of it, not an afterthought.
   3. Depth is spent once, on the evidence: the stage a screenshot sits on.
      Nothing else on the page is a card.
   4. The serif appears four times — the name, the project titles, the two
      closing statements. Everywhere else is the grotesque.
   5. Nothing moves except the bar, marking where you are.
   ========================================================================== */

/* --- tokens ---------------------------------------------------------------
   Dark first: the bare :root is the dark palette, and light is re-declared
   under an explicit choice or a light OS. */

:root {
  --ground:   #0a0a0b;
  --raise:    #141416;   /* the stage the evidence sits on */
  --ink:      #f5f5f6;
  --ink-2:    #a2a2a8;
  --ink-3:    #84848c;   /* 5.3:1 on --ground */
  --line:     #f5f5f61f;
  --line-2:   #f5f5f610;
  --bar:      #0a0a0bd4;
  --lift:     0 2px 8px #00000066, 0 28px 60px -24px #000000cc;
  --sheen:    radial-gradient(120% 80% at 50% -10%, #ffffff12, transparent 60%);

  --serif: "Instrument Serif", "Iowan Old Style", Palatino, Georgia, serif;
  --sans:  "Schibsted Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --measure: 36rem;
  --sheet:   64rem;
  --gutter:  clamp(1.25rem, 5vw, 3.5rem);
  --bar-h:   3.5rem;

  /* Static film grain. It is a texture, not an animation — the hero has
     depth without anything on it moving. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");

  color-scheme: dark light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ground: #f7f7f8;
    --raise:  #eaeaec;
    --ink:    #0a0a0b;
    --ink-2:  #55555c;
    --ink-3:  #6f6f77;   /* 4.7:1 on --ground */
    --line:   #0a0a0b20;
    --line-2: #0a0a0b0d;
    --bar:    #f7f7f8d4;
    --lift:   0 1px 2px #0a0a0b0f, 0 22px 48px -22px #0a0a0b3d;
    --sheen:  radial-gradient(120% 80% at 50% -10%, #0a0a0b0a, transparent 60%);
  }
}

:root[data-theme="light"] {
  --ground: #f7f7f8;
  --raise:  #eaeaec;
  --ink:    #0a0a0b;
  --ink-2:  #55555c;
  --ink-3:  #6f6f77;   /* 4.7:1 on --ground */
  --line:   #0a0a0b20;
  --line-2: #0a0a0b0d;
  --bar:    #f7f7f8d4;
  --lift:   0 1px 2px #0a0a0b0f, 0 22px 48px -22px #0a0a0b3d;
  --sheen:  radial-gradient(120% 80% at 50% -10%, #0a0a0b0a, transparent 60%);
}

/* --- reset --------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--bar-h) + 1.5rem);
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-feature-settings: "kern", "liga";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;   /* the full-bleed stages measure against the viewport */
}

img { max-width: 100%; height: auto; display: block; }
[hidden] { display: none !important; }

/* --- language switching --------------------------------------------------
   Both languages ship in the markup; CSS reveals one. No JS needed to read
   the page, and view-source stays legible. */

[data-lang="en"] [lang="nb"] { display: none; }
[data-lang="nb"] [lang="en"] { display: none; }


/* --- selection -----------------------------------------------------------
   The page never defined this, so a drag got the browser's system highlight —
   usually a saturated blue — laid over a near-black ground, and over the grain
   layer, which composites with mix-blend-mode and dirties it further. On a
   page with no accent colour that reads as a rendering fault rather than a
   selection. Ink on paper, inverted, in both themes. */

::selection {
  background: var(--ink);
  color: var(--ground);
  text-shadow: none;
}
::-moz-selection {
  background: var(--ink);
  color: var(--ground);
  text-shadow: none;
}
/* The grain sits above the text and blends; excluding it keeps a selection
   crisp instead of tinting it. */
.hero::after, .field::after { user-select: none; }

/* --- focus & skip -------------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 3px;
}

.skip {
  position: fixed; top: 0; left: 0; z-index: 100;
  padding: .7rem 1.1rem;
  background: var(--ink); color: var(--ground);
  font: 500 .85rem/1 var(--sans);
  text-decoration: none;
  transform: translateY(-120%);
}
.skip:focus { transform: none; }

/* --- the bar -------------------------------------------------------------
   It starts invisible over the opening field and materialises once the hero
   is behind you, so the first screen is only the name. */

.topbar {
  position: fixed; inset: 0 0 auto;
  z-index: 50;
  height: var(--bar-h);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .35s ease, border-color .35s ease, backdrop-filter .35s ease;
}
.topbar.is-scrolled {
  background: var(--bar);
  border-bottom-color: var(--line);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  backdrop-filter: saturate(180%) blur(16px);
}
@media (prefers-reduced-motion: reduce) { .topbar { transition: none; } }

.bar {
  max-width: var(--sheet);
  height: 100%;
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: flex; align-items: center; gap: 1.25rem;
}

.wordmark {
  font-weight: 600;
  font-size: .95rem;
  letter-spacing: -.015em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  margin-right: auto;
}

.secnav { display: flex; gap: clamp(.8rem, 2.2vw, 1.6rem); }
.secnav a {
  position: relative;
  padding: .25rem 0;
  font-size: .85rem;
  font-weight: 500;
  color: var(--ink-3);
  text-decoration: none;
  white-space: nowrap;
  transition: color .2s ease;
}
.secnav a:hover { color: var(--ink); }
/* the one moving mark: a hairline that grows under the section you are in */
.secnav a::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -.15rem;
  height: 1.5px; border-radius: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform .34s cubic-bezier(.22,.61,.36,1);
}
.secnav a[aria-current="true"],
.secnav a[aria-current="page"] { color: var(--ink); }
.secnav a[aria-current="true"]::after,
.secnav a[aria-current="page"]::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) { .secnav a::after { transition: none; } }

/* Below the breakpoint the section links would crowd the switches out; the
   wordmark still returns you to the top. */
@media (max-width: 53.999rem) { .secnav { display: none; } }

.controls {
  display: flex; gap: .4rem; align-items: center;
  margin-left: clamp(.9rem, 2.5vw, 2rem);
}

.controls button {
  font: 500 .7rem/1 var(--mono);
  letter-spacing: .05em;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid var(--line);
  padding: .42rem .55rem;
  cursor: pointer;
  transition: color .15s, border-color .15s, background-color .15s;
}
.controls button:hover { color: var(--ink); border-color: var(--ink-3); }

.seg { display: flex; }
.seg button:first-child { border-radius: 5px 0 0 5px; }
.seg button:last-child  { border-radius: 0 5px 5px 0; margin-left: -1px; }
.seg button[aria-pressed="true"] {
  color: var(--ground);
  background: var(--ink);
  border-color: var(--ink);
  position: relative; z-index: 1;
}
.ghost { border-radius: 5px; }

/* The button names the theme it will switch *to*, not the one you are in.
   Dark is the default ground, so it offers Light until told otherwise. */
.to-dark { display: none; }
:root[data-theme="light"] .to-dark  { display: inline; }
:root[data-theme="light"] .to-light { display: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .to-dark  { display: inline; }
  :root:not([data-theme="dark"]) .to-light { display: none; }
}

/* --- the opening field ---------------------------------------------------
   Full width, just under a screen tall so the first section shows its edge.
   The grain gives it depth without anything on it moving. */

.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;   /* the mobile browser chrome must not crop the name */
  padding: calc(var(--bar-h) + 2rem) var(--gutter) clamp(1.5rem, 4vh, 2.5rem);
  display: grid;
  grid-template-rows: 1fr auto;
  align-items: center;
  isolation: isolate;
  border-bottom: 1px solid var(--line);
}
.hero::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: var(--sheen);
}
/* The ridges sit under the grain and over the sheen. The canvas is sized in
   CSS and its backing store in JS, so it stays sharp on a retina screen. */
.ridge {
  position: absolute; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  display: block;
}

.hero::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background-image: var(--grain);
  background-size: 200px 200px;
  opacity: .16;
  mix-blend-mode: overlay;
  pointer-events: none;
}
:root[data-theme="light"] .hero::after { opacity: .1; mix-blend-mode: multiply; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .hero::after { opacity: .1; mix-blend-mode: multiply; }
}

.hero-in {
  position: relative;
  max-width: var(--sheet);
  width: 100%;
  margin: 0 auto;
  text-align: center;
  align-self: center;
}

.hero-foot { position: relative; }

.eyebrow {
  margin: 0 0 clamp(1.25rem, 3.5vh, 2rem);
  font: 400 .7rem/1 var(--mono);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
}

h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(3.4rem, 13vw, 9rem);
  line-height: .92;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.role {
  margin: clamp(1rem, 2.5vh, 1.6rem) 0 0;
  font-size: clamp(1rem, 2vw, 1.15rem);
  font-weight: 400;
  letter-spacing: .01em;
  color: var(--ink-2);
}

/* The affiliation answers what a name and a discipline leave open: who he does
   it for. It is set like the eyebrow above the name rather than as a third
   headline, so the two of them frame the name as a pair — and it is a link,
   because a studio you can open is a check the reader can make. */
.affil {
  margin: clamp(.85rem, 2vh, 1.2rem) 0 0;
  font: 400 .7rem/1.6 var(--mono);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.affil a {
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: .15em;
  transition: color .2s ease, border-color .2s ease;
}
.affil a:hover { color: var(--ink); border-bottom-color: var(--ink-3); }
@media (prefers-reduced-motion: reduce) { .affil a { transition: none; } }

/* Nothing but the cue lives down here now, so it sits on the centre line
   under the name rather than off in a corner on its own. */
.hero-foot {
  max-width: var(--sheet);
  width: 100%;
  margin: 0 auto;
  display: grid;
  justify-items: center;
}

.cue {
  display: grid; place-items: center;
  width: 2.6rem; height: 2.6rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--ink-2);
  text-decoration: none;
  font-size: 1rem;
  transition: color .2s ease, border-color .2s ease;
}
.cue:hover { color: var(--ink); border-color: var(--ink-3); }


/* --- arriving on a page --------------------------------------------------
   Cross-document view transitions where the browser has them (the
   @view-transition rule sits in each page's head), and this as the floor
   everywhere else. No fill mode: if the animation never runs, the content is
   simply there — the same rule the panel reveal learned the hard way. */

@keyframes page-in {
  from { opacity: 0; transform: translateY(10px); }
}
main, .hero-in, .hero-foot {
  animation: page-in .55s cubic-bezier(.22, .61, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
  main, .hero-in, .hero-foot { animation: none; }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* --- the two doors -------------------------------------------------------
   The home page's whole job is to be the shortest route into the work, so it
   offers two doors and gets out of the way. Each shows what is behind it
   rather than describing it: two screenshots, the name, and a count. */

.doors {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  padding-top: clamp(3rem, 9vh, 5.5rem);
}
@media (min-width: 52rem) { .doors { grid-template-columns: 1fr 1fr; } }

.door {
  position: relative;
  display: grid;
  align-content: end;
  gap: .3rem;
  min-height: clamp(19rem, 42vh, 27rem);
  padding: clamp(1.25rem, 3vw, 2rem);
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--raise);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  isolation: isolate;
  transition: border-color .3s ease;
}
.door:hover { border-color: var(--ink-3); }

/* Two shots, the back one peeking out from behind the front — enough to say
   "there is more than one of these" without a caption saying so. */
.door-shot { position: absolute; inset: 0; z-index: -1; }
.door-shot img {
  position: absolute;
  border-radius: 8px;
  border: 1px solid var(--line);
  box-shadow: var(--lift);
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
}
.door-shot img:first-child {
  width: 78%; left: 6%; top: 8%;
  transform: rotate(-2deg);
}
.door-shot img:last-child {
  width: 62%; right: 4%; top: 30%;
  transform: rotate(2.5deg);
}
@media (prefers-reduced-motion: no-preference) {
  .door:hover .door-shot img:first-child { transform: rotate(-2deg) translateY(-8px) scale(1.02); }
  .door:hover .door-shot img:last-child  { transform: rotate(2.5deg) translateY(-4px) scale(1.02); }
}
/* the ground the names sit on, so they stay readable over the shots */
.door::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top,
    var(--raise) 22%,
    color-mix(in srgb, var(--raise) 72%, transparent) 45%,
    transparent 72%);
}

.door-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 5vw, 3rem);
  line-height: 1;
  letter-spacing: -.015em;
}
.door-n { font-size: .95rem; color: var(--ink-2); }
.door-n b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.5em;
  line-height: .8;
  color: var(--ink);
  margin-right: .1em;
}


/* --- the closing field ---------------------------------------------------
   The page ends the way it began. Everything that used to sit here — an about
   grid, a method essay, a column of contact rows — was the same shape repeated
   six times down the page, which is both what makes a page feel generated and
   what buries the one thing a visitor came to find. This is that one thing. */

.field {
  position: relative;
  min-height: min(78vh, 40rem);
  margin-inline: calc(50% - 50vw);
  padding: clamp(4rem, 12vh, 7rem) var(--gutter);
  display: grid;
  place-items: center;
  text-align: center;
  isolation: isolate;
  border-top: 1px solid var(--line);
  overflow: hidden;
}
.field::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background-image: var(--grain);
  background-size: 200px 200px;
  opacity: .16;
  mix-blend-mode: overlay;
  pointer-events: none;
}
:root[data-theme="light"] .field::after { opacity: .1; mix-blend-mode: multiply; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .field::after { opacity: .1; mix-blend-mode: multiply; }
}

.field-in { position: relative; display: grid; gap: clamp(1rem, 3vh, 1.75rem); }

.field-status {
  margin: 0;
  font: 400 .7rem/1 var(--mono);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.field-status::before { vertical-align: .08em; }

/* The address is the largest thing on the page after the name. It is the
   whole point of the page, so it is allowed to be. */
.mailto {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.75rem, 6.5vw, 4.25rem);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--ink);
  text-decoration: none;
  word-break: break-word;
  border-bottom: 1px solid var(--line);
  padding-bottom: .12em;
  transition: border-color .25s ease;
}
.mailto:hover { border-bottom-color: var(--ink); }

.field-meta {
  margin: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem .75rem;
  font-size: .92rem;
  color: var(--ink-3);
}
.field-meta a { color: var(--ink-2); }
.field-meta a:hover { color: var(--ink); }

/* --- the three languages -------------------------------------------------
   Shown, not claimed. This is the one block on the page that ignores the
   EN/NO switch on purpose: all three lines are always visible, because the
   point is that all three exist. */

#tongues {
  padding: clamp(4rem, 11vh, 6.5rem) 0 clamp(4rem, 11vh, 6.5rem);
  display: grid;
  gap: .1rem;
}
[data-lang] #tongues .tongue { display: block; }
.tongue {
  display: block;
  width: 100%;
  margin: 0;
  padding: .1rem 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 4vw, 2.6rem);
  line-height: 1.22;
  letter-spacing: -.015em;
  color: var(--ink-3);
  transition: color .3s ease;
}
.tongue:hover { color: var(--ink); }

.tongue-note {
  margin: clamp(1.25rem, 3vh, 2rem) 0 0;
  font-size: .92rem;
  color: var(--ink-3);
}

/* the line you have opened leads; the other two step back */
#tongues[data-enhanced] .tongue[aria-expanded="true"] { color: var(--ink); }
#tongues[data-enhanced]:has(.tongue[aria-expanded="true"]) .tongue[aria-expanded="false"] {
  color: var(--ink-3);
}

/* --- what is behind a sentence ------------------------------------------
   Open by default, so with the script blocked all three introductions are
   simply there. main.js stamps the section and closes them — the enhancement
   hides, it never reveals. Same rule as the project panels. */

.bio {
  max-width: 34rem;
  padding: clamp(1rem, 2.5vh, 1.5rem) 0 clamp(1.5rem, 4vh, 2.25rem);
}
[data-lang] #tongues .bio { display: block; }
#tongues[data-enhanced] .bio { display: none; }
#tongues[data-enhanced] .bio.is-open { display: block; }

.bio p {
  margin: 0;
  font-size: clamp(1rem, 1.9vw, 1.12rem);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}
.bio p + p { margin-top: .7rem; }

/* The lines arrive one after another. Position only — never opacity, and
   never a clip the line starts outside of: an animation that stalls at its
   first frame must not be able to hide the text. Stalled, this is 10px low
   and perfectly readable. */
@keyframes line-in { from { transform: translateY(10px); } }

#tongues[data-enhanced] .bio.is-open p span {
  display: block;
  animation: line-in .5s cubic-bezier(.22, .61, .36, 1);
}
.bio.is-open p:nth-child(1) span { animation-delay: .00s; }
.bio.is-open p:nth-child(2) span { animation-delay: .07s; }
.bio.is-open p:nth-child(3) span { animation-delay: .14s; }
.bio.is-open p:nth-child(4) span { animation-delay: .21s; }

@media (prefers-reduced-motion: reduce) {
  #tongues[data-enhanced] .bio.is-open p span { animation: none; }
}
/* the line matching the language you are reading the site in leads */
[data-lang="en"] .tongue[lang="en"],
[data-lang="nb"] .tongue[lang="nb"] { color: var(--ink); }

/* --- page frame ---------------------------------------------------------- */

.sheet {
  max-width: var(--sheet);
  margin: 0 auto;
  padding: 0 var(--gutter) 6rem;
}

/* The prose column is narrower than the sheet on purpose: the evidence is
   allowed to run wider than the argument. */
.prose { max-width: var(--measure); }
.prose > p { margin: 1.5rem 0 0; text-wrap: pretty; }
.prose > p:first-child { margin-top: 0; }

.sec {
  margin: 0 0 clamp(1.5rem, 4vh, 2.25rem);
  padding-top: clamp(4rem, 11vh, 7rem);
  font: 400 .7rem/1 var(--mono);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-3);
}
.sec::after {
  content: ""; display: block;
  height: 1px; background: var(--line);
  margin-top: .95rem;
}
#sites > .sec { padding-top: clamp(3rem, 8vh, 4.5rem); }

.sec-lede {
  max-width: var(--measure);
  margin: 0 0 clamp(2.5rem, 6vh, 4rem);
  font-size: clamp(1.05rem, 2vw, 1.18rem);
  line-height: 1.5;
  color: var(--ink-2);
  text-wrap: pretty;
}


/* --- the websites track --------------------------------------------------
   The only thing on the page that moves sideways. It is an ordinary scroll
   container with snap points: trackpad, touch, arrow keys and the two buttons
   all drive it, and with JavaScript blocked it is still a scrollable row.
   The wheel is never hijacked — scrolling down the page always scrolls down. */

.track-head {
  display: grid;
  gap: 1.5rem;
  align-items: end;
}
@media (min-width: 48rem) {
  .track-head { grid-template-columns: minmax(0, 1fr) auto; }
}
.track-head .sec { padding-top: clamp(3rem, 8vh, 4.5rem); }
.track-head .sec-lede { margin-bottom: 0; }

/* The counter and the arrows are driven entirely by main.js, so they stay
   hidden until it has stamped the section. Without the script the row is
   still scrollable by touch and trackpad — but a counter reading "1 / 1" and
   two buttons that do nothing would be worse than no controls at all. */
.track-nav { display: none; }
[data-enhanced] .track-nav {
  display: flex; align-items: center; gap: 1rem;
  padding-bottom: .2rem;
}
.track-count {
  margin: 0;
  font: 400 .72rem/1 var(--mono);
  letter-spacing: .12em;
  color: var(--ink-3);
}
.track-count .num:first-child { color: var(--ink); }

.track-btns { display: flex; gap: .4rem; }
.tbtn {
  display: grid; place-items: center;
  width: 2.4rem; height: 2.4rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--ink-2);
  font-size: .95rem;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, opacity .2s ease;
}
.tbtn:hover:not([aria-disabled="true"]) { color: var(--ink); border-color: var(--ink-3); }
.tbtn[aria-disabled="true"] { opacity: .3; cursor: default; }

.track {
  /* The inset lines the first card up with the section heading above it, and
     scroll-padding repeats it — without that, the snap point is the card's own
     edge and the browser scrolls the inset away on load. */
  --track-inset: max(var(--gutter), calc(50vw - var(--sheet) / 2 + var(--gutter)));

  margin-inline: calc(50% - 50vw);
  margin-top: clamp(2rem, 5vh, 3rem);
  padding-inline: var(--track-inset);
  scroll-padding-inline: var(--track-inset);
  display: flex;
  gap: clamp(.75rem, 1.6vw, 1.25rem);
  padding-block: .5rem;   /* room for the hover lift */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* proximity, not mandatory: the buttons page by a screenful, and hard
     snapping would drag every page back onto a card boundary. */
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.track:focus-visible { outline-offset: -2px; }
.track::-webkit-scrollbar { height: 6px; }
.track::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }

/* --- a card in the row ---------------------------------------------------
   Small on purpose: the row is an index, not the work. Everything a card
   claims is a fact you can check without opening it — who it was for, what
   trade, what it runs on. */

.pcard {
  flex: 0 0 min(78vw, 19rem);
  /* no "read more" line: the whole card is the link, and the arrow was
     furniture. The open state is carried by the border instead. */
  /* #sites overrides this below: the two client sites are the sales argument,
     so they get the bigger card and the row that visibly runs off the edge. */
  scroll-snap-align: start;
  display: grid;
  gap: .85rem;
  padding: .75rem .75rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--raise);
  color: inherit;
  text-decoration: none;
  transition: border-color .2s ease, transform .3s cubic-bezier(.22,.61,.36,1);
}
.pcard:hover { border-color: var(--ink-3); }
@media (prefers-reduced-motion: no-preference) {
  .pcard:hover { transform: translateY(-3px); }
}
.pcard.is-open { border-color: var(--ink); }

#sites .pcard { flex-basis: min(84vw, 30rem); }
#sites .pcard-name { font-size: 1.25rem; }

[data-enhanced].no-scroll .track-nav { display: none; }

.pcard-shot {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 8px;
  border: 1px solid var(--line-2);
  background: var(--ground);
}
.pcard-shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
}

.pcard-text { display: block; }
.pcard-name {
  display: block;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -.015em;
  line-height: 1.25;
}
.pcard-sub {
  display: block;
  margin-top: .2rem;
  font: 400 .72rem/1.4 var(--mono);
  color: var(--ink-3);
}

/* --- the write-up a card opens ------------------------------------------
   With the script blocked these are simply all open, which is the honest
   fallback: the enhancement collapses them, it does not reveal them. */

.details { margin-top: clamp(2rem, 5vh, 3rem); }
.detail + .detail { margin-top: clamp(2rem, 5vh, 3rem); }

[data-enhanced] .details { margin-top: 0; }
[data-enhanced] .detail { display: none; margin-top: 0; }
[data-enhanced] .detail.is-open {
  display: block;
  border-top: 1px solid var(--line);
  animation: reveal .4s cubic-bezier(.22,.61,.36,1);
}

/* Two rules learned the hard way, both about motion that can hide content.
   The height is not interpolated: the 0fr/1fr grid trick resolves to zero
   here (an auto-height grid has no free space, and overflow:hidden takes the
   item's automatic minimum down with it), so the panel opened to exactly its
   own padding. And the reveal animates position only, never opacity — an
   animation that stalls at its first frame would otherwise leave the panel
   present, occupying its full height, and invisible. */
@keyframes reveal {
  from { transform: translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
  [data-enhanced] .detail.is-open { animation: none; }
}

.detail-in { padding-top: clamp(2rem, 5vh, 3rem); }

/* Inside a panel the media leads and the argument follows it. */
.detail .wcard-body { margin-top: clamp(2rem, 5vh, 3rem); }
.detail .wcard-body h3, .detail .case-head h3 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 5vw, 3.1rem);
  line-height: 1;
  letter-spacing: -.015em;
  text-wrap: balance;
}
.detail .prose { margin-top: clamp(1.5rem, 4vh, 2.25rem); }

.detail-close { margin: clamp(2rem, 5vh, 3rem) 0 0; }
.detail-close button {
  font: 500 .85rem/1.4 var(--sans);
  color: var(--ink-3);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: .22em;
  transition: color .2s ease;
}
.detail-close button:hover { color: var(--ink); }

/* Inside a panel the stage is a panel, not a full-bleed band. */
.detail .stage {
  margin-inline: 0;
  padding: clamp(1rem, 2.5vw, 2rem);
  border: 1px solid var(--line);
  border-radius: 16px;
  display: grid;
  gap: clamp(1rem, 2vw, 1.5rem);
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
}
@media (min-width: 42rem) {
  .detail .stage { grid-template-columns: minmax(0, 1fr) minmax(0, 9rem); align-items: end; }
}
.detail .stage .mob { max-width: 9rem; }
@media (max-width: 41.999rem) { .detail .stage .mob { max-width: 7rem; } }

/* --- the stage the evidence sits on -------------------------------------- */

.stage {
  margin-inline: calc(50% - 50vw);
  padding: clamp(1.75rem, 5vw, 4.5rem) var(--gutter);
  background: var(--raise);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  justify-items: center;
  justify-content: center;
}
@media (min-width: 62rem) {
  .stage {
    grid-template-columns: minmax(0, 58rem) minmax(0, 13rem);
    align-items: end;
  }
}
.stage img {
  border-radius: 10px;
  border: 1px solid var(--line);
  box-shadow: var(--lift);
}
.stage .desk { width: 100%; }
.stage .mob  { width: 100%; max-width: 11rem; border-radius: 16px; }
@media (min-width: 62rem) { .stage .mob { max-width: none; } }

.wcard-body h3, .case-head h3 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.4rem, 6vw, 4rem);
  line-height: 1;
  letter-spacing: -.015em;
  text-wrap: balance;
}

.sub {
  margin: .9rem 0 0;
  max-width: var(--measure);
  font-size: clamp(1.1rem, 2.2vw, 1.32rem);
  line-height: 1.4;
  letter-spacing: -.01em;
  color: var(--ink-2);
  text-wrap: pretty;
}

.visit { margin: clamp(1.75rem, 4vh, 2.5rem) 0 0; }
.visit a {
  font: 500 .95rem/1 var(--mono);
  letter-spacing: .02em;
}

/* --- the specification strip --------------------------------------------- */

.specs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1.5rem clamp(1.25rem, 3vw, 2.5rem);
  margin: clamp(2rem, 5vh, 2.75rem) 0 0;
  padding: 1.5rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.specs > div.wide { grid-column: 1 / -1; }
.specs dt {
  font: 400 .65rem/1 var(--mono);
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .5rem;
}
.specs dd { margin: 0; font-size: .92rem; line-height: 1.4; color: var(--ink-2); }
.specs dd a { color: inherit; text-decoration-color: var(--line); }
.specs dd a:hover { color: var(--ink); text-decoration-color: currentColor; }

.chips { list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .35rem; }
.chips li {
  font: 400 .68rem/1 var(--mono);
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: .36rem .48rem;
  white-space: nowrap;
}

/* "live" is a state, so it is marked with a dot rather than a colour — there
   is no accent hue on this page to spend on it. */
.live { color: var(--ink); }
.live::before {
  content: ""; display: inline-block;
  width: .42em; height: .42em; margin-right: .55em;
  border-radius: 50%; background: currentColor;
  vertical-align: .12em;
}

.num { font-variant-numeric: tabular-nums lining-nums; }

/* --- software cases ------------------------------------------------------ */

.case-head h3 { font-size: clamp(2.1rem, 5vw, 3.1rem); }

.prose em { font-style: italic; }
.prose code {
  font: 400 .86em/1 var(--mono);
  background: var(--raise);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: .14em .34em;
  white-space: nowrap;
}

figure { margin: clamp(2.25rem, 6vh, 3.5rem) 0 0; max-width: 52rem; }
figure.wide { max-width: none; }

.frame {
  background: var(--raise);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: clamp(.6rem, 1.6vw, 1.25rem);
  box-shadow: var(--lift);
}
.frame img {
  width: 100%;
  border-radius: 10px;
  border: 1px solid var(--line-2);
}

/* the phone shot is small; it gets a wider stage instead of a smaller frame,
   so its edges still line up with every other piece of evidence */
figure.phone .frame {
  display: grid; place-items: center;
  padding: clamp(1.75rem, 5vw, 3.5rem);
}
figure.phone .frame img {
  width: auto; max-width: 16rem;
  border-radius: 22px;
}

figcaption {
  margin-top: 1rem;
  max-width: var(--measure);
  font-size: .86rem;
  line-height: 1.55;
  color: var(--ink-3);
  text-wrap: pretty;
}

/* the one hard fact per project — set apart by a rule, not a box */
.fact {
  margin: clamp(2rem, 5vh, 2.75rem) 0 0;
  padding-left: 1.15rem;
  border-left: 2px solid var(--ink);
  font-size: 1rem;
  color: var(--ink-2);
  text-wrap: pretty;
}
.fact b { color: var(--ink); font-weight: 600; }

/* progressive disclosure */
details {
  margin-top: 2.25rem;
  border-top: 1px solid var(--line);
}
summary {
  cursor: pointer;
  list-style: none;
  padding: 1rem 0 0;
  font: 500 .88rem/1 var(--sans);
  color: var(--ink);
  display: flex; align-items: center; gap: .55rem;
}
summary::-webkit-details-marker { display: none; }
summary::before {
  content: "+";
  font: 400 .95rem/1 var(--mono);
  width: .8rem;
  color: var(--ink-3);
}
details[open] summary::before { content: "\2212"; }
details > div > p { margin: 1.2rem 0 0; font-size: .96rem; color: var(--ink-2); text-wrap: pretty; }
details > div { padding-bottom: .5rem; }


/* --- the counts ---------------------------------------------------------
   These replaced the explanatory paragraph, and then replaced the stat row
   that replaced it. A big numeral over a small uppercase label, three across,
   is the most-generated component of the last few years — and it read worst
   exactly where it mattered, two zeroes side by side saying nothing.

   So the numbers are a sentence that happens to open with a numeral. Same
   figures, same checkability, but it reads as a claim rather than a
   dashboard tile. Every one is verifiable against the project's own output. */

.count {
  margin: clamp(1.75rem, 4vh, 2.5rem) 0 0;
  max-width: 42rem;
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  line-height: 1.75;
  color: var(--ink-2);
  text-wrap: pretty;
}
.count b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.9em;
  line-height: .8;
  letter-spacing: -.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
  margin-right: .03em;
}

/* --- the creed -----------------------------------------------------------
   Three statements, set large. It used to be three essays; the argument is
   the same and the reader gets it in a tenth of the words. */

.creed { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.creed li {
  padding: clamp(2rem, 5vh, 3rem) 0;
  border-top: 1px solid var(--line-2);
  display: grid;
  gap: .75rem;
}
.creed li:first-child { border-top: none; padding-top: 0; }
@media (min-width: 52rem) {
  .creed li { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: clamp(2rem, 5vw, 4rem); align-items: baseline; }
}
.creed h3 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.9rem, 4.2vw, 2.9rem);
  line-height: 1.05;
  letter-spacing: -.015em;
  text-wrap: balance;
}
.creed p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ink-2);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* --- about ---------------------------------------------------------------
   Split into blocks rather than run as prose: these are the things a client
   checks before reading anything, so they have to be scannable. The languages
   block is the widest because it is the one people actually ask about. */

.about-lede { margin-top: clamp(1.5rem, 4vh, 2.25rem); }

.blocks {
  display: grid;
  gap: 1px;                      /* the hairlines are the gaps */
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
  margin-top: clamp(2.5rem, 6vh, 3.5rem);
}
/* Three blocks in two columns leaves an empty cell, and the gap-as-hairline
   trick renders that hole as a grey rectangle. Three across fills the row. */
@media (min-width: 52rem) {
  .blocks { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .block-wide { grid-column: 1 / -1; }
}

.block {
  background: var(--ground);
  padding: clamp(1.35rem, 3vw, 1.9rem);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.block-h {
  margin: 0;
  font: 400 .67rem/1 var(--mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.block-note {
  margin: auto 0 0;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--ink-3);
  text-wrap: pretty;
}

.block-live { margin: 0; font-size: 1.02rem; }

@media (min-width: 46rem) {
  .block-wide .rows { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 clamp(1.5rem, 4vw, 3rem); }
  .block-wide .rows > div:nth-child(2) { border-top: none; padding-top: 0; }
}

/* the languages list */
.lang { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
.lang li {
  display: grid;
  gap: .15rem .9rem;
  padding: .8rem 0;
  border-top: 1px solid var(--line-2);
  align-items: baseline;
}
.lang li:first-child { border-top: none; padding-top: 0; }
@media (min-width: 34rem) {
  .lang li { grid-template-columns: 8rem 6rem minmax(0, 1fr); }
}
.lang-n {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -.01em;
}
.lang-l {
  font: 400 .7rem/1.6 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.lang-u { font-size: .9rem; line-height: 1.5; color: var(--ink-2); text-wrap: pretty; }

/* label/value rows */
.rows { margin: 0; display: grid; gap: 0; }
.rows > div {
  display: grid;
  gap: .1rem .9rem;
  padding: .7rem 0;
  border-top: 1px solid var(--line-2);
  align-items: baseline;
}
.rows > div:first-child { border-top: none; padding-top: 0; }
@media (min-width: 26rem) {
  .rows > div { grid-template-columns: 6.5rem minmax(0, 1fr); }
}
.rows dt {
  font: 400 .67rem/1.6 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.rows dd { margin: 0; font-size: .92rem; line-height: 1.45; color: var(--ink-2); }

/* the "what I take on" list — a rule per item, no bullet characters */
.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.ticks li {
  padding: .6rem 0;
  border-top: 1px solid var(--line-2);
  font-size: .95rem;
  color: var(--ink-2);
}
/* Each item carries both languages, so the first item is genuinely the first
   child in either. Selecting it by :first-of-type instead would silently fail:
   :first-of-type is positional and ignores the attribute filter, so
   li[lang="nb"]:first-of-type never matched anything. */
.ticks li:first-child { border-top: none; padding-top: 0; }

/* --- footer -------------------------------------------------------------- */

footer { padding-bottom: 2rem; }
/* On a work page the contact block is the way onward, so it is separated by a
   rule rather than announced with its own section label. */
main + footer { margin-top: clamp(3rem, 8vh, 5rem); border-top: 1px solid var(--line-2); }
main + footer .sec { padding-top: clamp(2.5rem, 6vh, 3.5rem); }

/* The two closing statements are the only other place the serif appears. */
.closing {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2.15rem);
  line-height: 1.22;
  letter-spacing: -.01em;
  color: var(--ink);
  text-wrap: pretty;
}

.links { list-style: none; margin: clamp(2.25rem, 5vh, 3rem) 0 0; padding: 0; }
.links li {
  display: grid; gap: .2rem 1.75rem;
  padding: .9rem 0;
  border-bottom: 1px solid var(--line-2);
}
@media (min-width: 42rem) {
  .links li { grid-template-columns: 10.5rem minmax(0, 1fr); align-items: baseline; }
}
.links .k {
  font: 400 .67rem/1.7 var(--mono);
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}
.links a, .links span:not(.k) { font-size: 1.02rem; }

/* No accent hue: a link is marked by the underline and by being full-ink. */
a { color: var(--ink); text-decoration-thickness: 1px; text-underline-offset: .22em; }
a:hover { text-decoration-thickness: 2px; }

.colophon {
  max-width: var(--measure);
  margin: clamp(3rem, 7vh, 4rem) 0 0;
  font-size: .82rem;
  line-height: 1.6;
  color: var(--ink-3);
  text-wrap: pretty;
}

/* --- print: a clean two-page CV -----------------------------------------
   "Send me your CV" is already answered by Ctrl+P. */

@media print {
  :root {
    --ground: #fff; --raise: #fff; --ink: #000; --ink-2: #2b2b2b; --ink-3: #666;
    --line: #bbb; --line-2: #e0e0e0; --lift: none; --sheen: none; --grain: none;
  }
  @page { margin: 14mm 13mm; }

  .topbar, .skip, details, figure, .stage, .cue, .ridge, .colophon,
  .doors, .field::after, .field .ridge { display: none !important; }

  body { font-size: 9pt; line-height: 1.4; background: #fff; overflow: visible; }
  .sheet { padding: 0; max-width: none; }
  .prose, .sub, .sec-lede, figcaption, .about-lede { max-width: none; }

  main, .hero-in, .hero-foot { animation: none !important; }
  #tongues { padding-top: 8pt; }
  .tongue { font-size: 9pt; color: #000; }
  .tongue-note { font-size: 7.8pt; }
  .field {
    min-height: 0; margin-inline: 0; padding: 8pt 0 0; display: block;
    text-align: left; border-top: 1px solid var(--line-2); break-inside: avoid;
  }
  .field-in { display: block; }
  .field-status { font-size: 5.6pt; }
  .mailto { font-size: 13pt; border: 0; }
  .field-meta { display: block; font-size: 8pt; margin-top: 3pt; }
  .hero { min-height: 0 !important; padding: 0 0 8pt; border-bottom: none; display: block; }
  .hero::before, .hero::after { content: none; }
  .hero-in { text-align: left; }
  h1 { font-size: 26pt; }
  .eyebrow { margin-bottom: 4pt; font-size: 6pt; }
  .role { font-size: 10pt; margin-top: 3pt; }
  .affil { font-size: 6pt; margin-top: 4pt; letter-spacing: .12em; }
  .affil a { border: 0; }
  .hero-foot { display: block; }

  /* a section label must not be left stranded at the foot of a page */
  .sec { margin-bottom: 5pt; padding-top: 10pt; font-size: 6pt; break-after: avoid; }
  .sec-lede { break-after: avoid; }
  .sec::after { margin-top: 4pt; }
  #sites > .sec { padding-top: 10pt; }
  .sec-lede { font-size: 8.2pt; margin-bottom: 5pt; }

  /* Each project is one block: title, one-line summary, the specification,
     and the single hard fact. The essay and the images stay on the web. */
  .track, .pcard, .track-nav, .detail-close { display: none !important; }
  [data-enhanced] .detail { display: block !important; grid-template-rows: none !important; }
  [data-enhanced] .detail-in { overflow: visible !important; }
  .detail-in { padding-top: 0; }
  .detail, .creed li {
    padding: 6pt 0 !important;
    border-top: none !important;
    border-bottom: 1px solid var(--line-2);
    break-inside: avoid;
  }
  .detail .wcard-body h3, .detail .case-head h3 { font-size: 12pt; }
  .detail .wcard-body { margin-top: 4pt; }
  .creed h3 { font-size: 10.5pt; margin-bottom: 2pt; }
  .creed p { font-size: 8.2pt; }
  .creed li { display: block; padding: 4pt 0; }
  .count { margin-top: 4pt; font-size: 8.6pt; line-height: 1.5; max-width: none; }
  .count b { font-size: 1.5em; }
  .sub { font-size: 8.6pt; margin-top: 2pt; }
  .visit { margin-top: 3pt; }
  .visit a { font-size: 8pt; }

  .specs {
    margin-top: 4pt; padding: 4pt 0;
    grid-template-columns: repeat(4, 1fr);
    gap: 4pt 12pt;
  }
  .specs dt, .links .k { font-size: 5.6pt; margin-bottom: 2pt; }
  .specs dd { font-size: 7.8pt; }
  .detail .prose { margin-top: 4pt; }
  .fact {
    margin-top: 5pt; font-size: 8.2pt;
    border-left: 1pt solid var(--ink); padding-left: 6pt;
  }
  .chips { gap: 2pt; }
  .chips li { font-size: 5.8pt; padding: 1.5pt 2.5pt; }

  .about-lede > p { font-size: 8.2pt; margin-top: 3pt; }
  .blocks {
    display: grid; grid-template-columns: 1fr 1fr; gap: 0 18pt;
    background: none; border: none; border-radius: 0; margin-top: 6pt;
  }
  .block { padding: 4pt 0; background: none; break-inside: avoid; gap: 4pt; }
  .block-h { font-size: 5.6pt; }
  .lang li, .rows > div, .ticks li { padding: 2pt 0; }
  .lang-n { font-size: 8.5pt; }
  .lang-l, .rows dt { font-size: 5.6pt; }
  .lang-u, .rows dd, .ticks li { font-size: 7.8pt; line-height: 1.3; }
  .block-note { font-size: 6.8pt; }

  .links li {
    padding: 3pt 0; display: grid; grid-template-columns: 74pt 1fr; gap: 0 14pt;
    break-inside: avoid;
  }
  .links a, .links span:not(.k) { font-size: 8.5pt; }
  .closing { font-size: 11pt; margin-top: 6pt; }
  footer { padding-bottom: 0; }

  a { text-decoration: none; color: #000; }
}
