/* jonasrackl.com — shared tokens */

:root {
  /* paper + ink (warm) */
  --bg:        #efebe1;
  --bg-paper:  #f5f1e7;
  --bg-deep:   #e6e0d2;
  --ink:       #1a1612;
  --ink-soft:  #5b544a;
  --ink-mute:  #978f80;
  --rule:      rgba(26, 22, 18, 0.14);
  --rule-soft: rgba(26, 22, 18, 0.07);

  /* gradient orb palette */
  --c-rust:   #c8543d;
  --c-ember:  #b13923;
  --c-amber:  #e2a25a;
  --c-rose:   #c66a6a;
  --c-blush:  #e6c7bd;
  --c-sky:    #8aa1bd;
  --c-indigo: #4a4d7a;
  --c-sage:   #8a9a7a;
  --c-cream:  #efddb8;
  --c-dusk:   #6b5a72;

  /* type scale */
  --f-sans: "Schibsted Grotesk", "Helvetica Neue", Arial, sans-serif;
  --f-serif: "Instrument Serif", "Times New Roman", serif;
  --f-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  /* the hero name face — swappable via tweak */
  --f-name: "Newsreader", "Source Serif", Georgia, serif;
  --f-name-weight: 500;
  --f-name-tracking: -0.022em;

  --grain-amount: 0.55;
}

/* ── grain ── a single tiled SVG noise (encoded once) ── */
.grain-layer {
  position: absolute; inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240' viewBox='0 0 240 240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch' seed='4'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.9 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  mix-blend-mode: multiply;
  opacity: var(--grain-amount);
  z-index: 50;
}
.grain-layer.fine {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180' viewBox='0 0 180 180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='2.0' numOctaves='1' stitchTiles='stitch' seed='8'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.8 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
}

/* ── grain styles ── 5 atmosphere treatments, chosen via tweak.
   All read brighter than the original heavy multiply-grain. The active
   style is selected by a class on <html>: s-paper, s-silver, s-dust,
   s-halation, s-cinema. The .grain element below is the surface; the
   modifiers swap its background / blend / mask. ── */
.grain {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 50;
  mix-blend-mode: multiply;
  opacity: var(--grain-amount);
  background-repeat: repeat;
}

/* s-paper — current organic, lighter. Fine grain modulated by a slow
   blotch so density varies across the page (silver-gelatin feel). */
html.s-paper .grain {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.5' numOctaves='2' stitchTiles='stitch' seed='5'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.55 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 200px 200px;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1280' height='1400' preserveAspectRatio='none'><filter id='b'><feTurbulence type='fractalNoise' baseFrequency='0.0035 0.0055' numOctaves='3' seed='11'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1.8 -0.95'/></filter><rect width='100%25' height='100%25' filter='url(%23b)'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1280' height='1400' preserveAspectRatio='none'><filter id='b'><feTurbulence type='fractalNoise' baseFrequency='0.0035 0.0055' numOctaves='3' seed='11'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1.8 -0.95'/></filter><rect width='100%25' height='100%25' filter='url(%23b)'/></svg>");
  -webkit-mask-size: 100% 100%;          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;        mask-repeat: no-repeat;
}

/* s-silver — uniform fine grain, no mask, very low alpha. Silver-gelatin
   contact print, even across the page. */
html.s-silver .grain {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220' viewBox='0 0 220 220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='2.2' numOctaves='1' stitchTiles='stitch' seed='12'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.42 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
}

/* s-dust — sparse high-contrast specks only, like aged celluloid dust.
   A discrete alpha curve keeps only the brightest 1/16 of pixels. */
html.s-dust .grain {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='280' height='280' viewBox='0 0 280 280'><filter id='d'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='1' stitchTiles='stitch' seed='9'/><feComponentTransfer><feFuncA type='discrete' tableValues='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.85'/></feComponentTransfer></filter><rect width='100%25' height='100%25' filter='url(%23d)'/></svg>");
  background-size: 280px 280px;
  opacity: calc(var(--grain-amount) * 1.4);
}

/* s-halation — warm light glow patches (16mm projector bulb hot-spots)
   plus a faint dust. Screen blend (additive light) so it brightens the
   paper rather than darkening it. Boosted opacity to compensate. */
html.s-halation .grain {
  background-image:
    radial-gradient(ellipse 360px 240px at 18% 22%, rgba(255,200,140,0.32), transparent 72%),
    radial-gradient(ellipse 460px 320px at 78% 64%, rgba(255,182,120,0.28), transparent 72%),
    radial-gradient(circle 180px at 50% 14%, rgba(255,224,170,0.24), transparent 75%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220' viewBox='0 0 220 220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.8' numOctaves='1' stitchTiles='stitch' seed='3'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.32 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 60% 60%, 70% 60%, 40% 30%, 220px 220px;
  background-position: 18% 22%, 78% 64%, 50% 14%, 0 0;
  background-repeat: no-repeat, no-repeat, no-repeat, repeat;
  mix-blend-mode: screen;
  opacity: calc(var(--grain-amount) * 1.7);
}

/* s-cinema — vertical streaks (gate-weave / projector scratch) plus dust.
   Anisotropic turbulence: tiny baseFreq on x, large on y → vertical-only. */
html.s-cinema .grain {
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1280' height='340' viewBox='0 0 1280 340' preserveAspectRatio='none'><filter id='v'><feTurbulence type='fractalNoise' baseFrequency='0.012 1.6' numOctaves='2' stitchTiles='stitch' seed='7'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.5 0'/></filter><rect width='100%25' height='100%25' filter='url(%23v)'/></svg>");
  background-size: 100% 340px;
  background-repeat: repeat-y;
  opacity: calc(var(--grain-amount) * 0.9);
}

/* legacy alias so existing markup keeps working during the rename */
.grain-organic { display: none; }

/* ── orb ── the signature device ── */
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(18px);
  pointer-events: none;
}
.orb::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220' viewBox='0 0 220 220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.3' numOctaves='2' stitchTiles='stitch' seed='2'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1.1 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
  mix-blend-mode: multiply;
  opacity: 0.7;
}

/* artboard base */
.art {
  position: relative;
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--f-sans);
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "ss01", "ss02";
}
.art * { box-sizing: border-box; }

/* small reusable atoms */
.label {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  color: var(--ink-soft);
}
.rule {
  background: var(--rule);
  width: 100%; height: 1px;
}
.rule-soft { background: var(--rule-soft); }

/* image slot styling override */
image-slot {
  --is-bg: var(--bg-deep);
  --is-border: rgba(26, 22, 18, 0.18);
  --is-text: var(--ink-soft);
}

/* ── orb-cluster drift ── 5 slow, desynced movements so the cluster
   actually reads as moving (was previously too subtle). Bumped to
   80–140px travel over 18–28s. Uses `translate` so it composes with
   each orb's existing transform: translateX(-50%) centering. Multi-
   keyframe paths (not just 0/50/100) give each orb a non-linear arc
   instead of a back-and-forth shuttle — feels more like floating. ── */
/* Much larger drift distances + multi-keyframe paths so orbs separate
   visibly from the cluster at peaks — they should read as individual
   "molecules" floating apart, not a single mass that wobbles. Each orb
   has its own asymmetric path and offset phase. */
@keyframes orb-drift-a {
  0%   { translate:    0px    0px; }
  25%  { translate:  180px  -70px; }
  45%  { translate:  240px   80px; }
  70%  { translate:   60px  160px; }
  100% { translate:    0px    0px; }
}
@keyframes orb-drift-b {
  0%   { translate:    0px    0px; }
  30%  { translate: -200px   90px; }
  55%  { translate: -260px  -50px; }
  80%  { translate:  -80px -140px; }
  100% { translate:    0px    0px; }
}
@keyframes orb-drift-c {
  0%   { translate:    0px    0px; }
  20%  { translate:  140px  140px; }
  50%  { translate:  220px  -60px; }
  75%  { translate:  -60px   80px; }
  100% { translate:    0px    0px; }
}
@keyframes orb-drift-d {
  0%   { translate:    0px    0px; }
  30%  { translate: -150px -180px; }
  60%  { translate:  100px -240px; }
  85%  { translate:  -40px  -60px; }
  100% { translate:    0px    0px; }
}
@keyframes orb-drift-e {
  0%   { translate:    0px    0px; }
  25%  { translate:  120px  200px; }
  50%  { translate: -100px  260px; }
  75%  { translate: -180px   60px; }
  100% { translate:    0px    0px; }
}

.orb-cluster-animated > .orb:nth-child(1) { animation: orb-drift-a 29s ease-in-out infinite    0s; will-change: translate; }
.orb-cluster-animated > .orb:nth-child(2) { animation: orb-drift-b 35s ease-in-out infinite   -8s; will-change: translate; }
.orb-cluster-animated > .orb:nth-child(3) { animation: orb-drift-c 24s ease-in-out infinite  -14s; will-change: translate; }
.orb-cluster-animated > .orb:nth-child(4) { animation: orb-drift-d 33s ease-in-out infinite   -4s; will-change: translate; }
.orb-cluster-animated > .orb:nth-child(5) { animation: orb-drift-e 26s ease-in-out infinite  -11s; will-change: translate; }

@media (prefers-reduced-motion: reduce) {
  .orb-cluster-animated > .orb { animation: none !important; }
}

/* ── interactive category index ── click-to-expand drawer.
   ──────────────────────────────────────────────────────────────
   ANIMATION SYSTEM. Everything that moves when a category opens,
   closes, dims, or is hovered shares ONE duration and ONE easing
   curve so the whole motion reads as a single gesture instead of
   several overlapping transitions of different lengths. To retune
   the entire animation feel, change just the two values below. ── */
:root {
  --cat-dur: 420ms;
  --cat-ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* dim siblings smoothly too */
.cat {
  cursor: pointer;
  border-top: 1px solid transparent;
  padding-top: 0;
  transition: opacity var(--cat-dur) var(--cat-ease);
}
.cat-row { display: contents; }
.cat.is-dim { opacity: 0.18; }
.cat-head {
  display: flex; align-items: baseline; gap: 14px;
  margin-bottom: 10px;
  transition: color 240ms ease;
}
.cat-name {
  font-family: var(--f-sans); font-weight: 500; font-size: 26px;
  letter-spacing: -0.02em; color: var(--ink);
  position: relative;
}
/* a subtle pull-string under the category name when not open */
.cat-name::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -3px;
  height: 1px; background: var(--ink);
  transform: scaleX(0); transform-origin: left;
  transition: transform 320ms cubic-bezier(0.4, 0, 0.1, 1);
}
.cat:hover .cat-name::after { transform: scaleX(0.18); }
.cat.is-open .cat-name::after { transform: scaleX(1); }

/* sub-tags row — becomes filter chips when the category is open */
.cat-subs {
  font-family: var(--f-mono); font-size: 11px;
  letter-spacing: 0.06em; text-transform: lowercase;
  color: var(--ink-mute);
  margin-bottom: 14px;
  display: flex; gap: 8px; flex-wrap: wrap;
  align-items: baseline;
}
.cat-sub {
  cursor: pointer;
  padding: 2px 0;
  position: relative;
  transition: color 200ms ease;
}
.cat-sub:hover { color: var(--ink-soft); }
.cat.is-open .cat-sub {
  padding: 2px 8px;
  border: 1px solid var(--rule);
  color: var(--ink-soft);
  transition: all 220ms ease;
}
.cat.is-open .cat-sub.is-active {
  background: var(--ink); color: var(--bg-paper);
  border-color: var(--ink);
}
.cat-sub-sep { color: var(--ink-mute); }
.cat.is-open .cat-sub-sep { display: none; }

/* ── Closed-state peek ──
   Hidden by default; appears only when hovering or keyboard-focusing
   a closed category. Uses the grid-rows 0fr↔1fr trick so the height
   animates cleanly and the layout reflows without snap. */
.cat-peek-wrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  pointer-events: none;
  transition: grid-template-rows var(--cat-dur) var(--cat-ease),
              opacity            var(--cat-dur) var(--cat-ease);
}
.cat:not(.is-open):hover .cat-peek-wrap,
.cat:not(.is-open):focus-within .cat-peek-wrap {
  grid-template-rows: 1fr;
  opacity: 1;
  pointer-events: auto;
}
.cat-peek {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: 4px;
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-size: 15px;
  line-height: 1.4;
  color: var(--ink-soft);
}
.cat-peek-item { display: flex; align-items: baseline; gap: 8px; }
.cat-peek-bullet { color: var(--ink-mute); font-style: normal; }
.cat-peek-title { flex: 1; }
.cat-peek-more {
  font-family: var(--f-mono);
  font-style: normal;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: lowercase;
  color: var(--ink-mute);
  margin-top: 8px;
}

/* Legacy single-line preview classes — kept as no-ops so any older
   markup elsewhere on the site degrades gracefully. */
.cat-preview, .cat-preview-meta { display: none; }

/* ── Drawer ──
   Same duration + easing as the peek so when you click a category
   the peek shrinking and the drawer growing land at the same
   moment, reading as one motion. */
.cat-drawer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--cat-dur) var(--cat-ease);
}
.cat.is-open .cat-drawer { grid-template-rows: 1fr; }
.cat-drawer-inner { overflow: hidden; min-height: 0; }

/* ── Drawer items ──
   No per-item stagger — staggering made the drawer feel like it
   stopped and the items dribbled in afterwards. Now all items fade
   in together, slightly delayed so they appear right as the drawer
   is finishing its expansion. */
.cat-item {
  display: grid; grid-template-columns: 1fr auto;
  gap: 16px; align-items: baseline;
  padding: 8px 0;
  border-top: 1px solid var(--rule-soft);
  opacity: 0;
  transition: opacity var(--cat-dur) var(--cat-ease);
}
.cat-item:first-child { border-top: 1px solid var(--rule); margin-top: 2px; }
.cat.is-open .cat-item {
  opacity: 1;
  transition-delay: calc(var(--cat-dur) * 0.45);
}

.cat-item-title { font-size: 15px; color: var(--ink); line-height: 1.4; }
.cat-item-sub {
  font-family: var(--f-mono); font-size: 10px;
  letter-spacing: 0.08em; text-transform: lowercase;
  color: var(--ink-soft); white-space: nowrap;
}
.cat-item:hover .cat-item-title { color: var(--ink); }

/* ── name-grain ── makes a heading text look like ink absorbed into
   rough paper. The mask is a noise field pushed through a contrast
   ramp + alpha pull so MOST pixels stay fully opaque (text shows)
   and a small fraction are transparent — paper colour pokes through
   the glyph fill, simulating fibres that didn't take ink. Combined
   with the SVG displacement filter on the element, the type reads as
   embedded in the paper rather than placed on top. ── */
.name-grain {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='320'><filter id='m'><feTurbulence type='fractalNoise' baseFrequency='2.6' numOctaves='1' stitchTiles='stitch' seed='5'/><feComponentTransfer><feFuncR type='linear' slope='2.6' intercept='-0.5'/><feFuncG type='linear' slope='2.6' intercept='-0.5'/><feFuncB type='linear' slope='2.6' intercept='-0.5'/></feComponentTransfer><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1 0 0 0 0'/></filter><rect width='100%25' height='100%25' filter='url(%23m)'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='320' height='320'><filter id='m'><feTurbulence type='fractalNoise' baseFrequency='2.6' numOctaves='1' stitchTiles='stitch' seed='5'/><feComponentTransfer><feFuncR type='linear' slope='2.6' intercept='-0.5'/><feFuncG type='linear' slope='2.6' intercept='-0.5'/><feFuncB type='linear' slope='2.6' intercept='-0.5'/></feComponentTransfer><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1 0 0 0 0'/></filter><rect width='100%25' height='100%25' filter='url(%23m)'/></svg>");
  -webkit-mask-size: 320px 320px;          mask-size: 320px 320px;
  -webkit-mask-repeat: repeat;              mask-repeat: repeat;
}

/* ── art-v11 — fades hero / cluster / stamp when any category drawer
   is open below. Without this, the drawer expands upward into the
   hero's vertical space and the two read on top of each other.
   Driven by data-cat-open="true" set by the React component. —— */
.art-v11 .v11-fade {
  transition: opacity var(--cat-dur) var(--cat-ease),
              transform var(--cat-dur) var(--cat-ease);
}
.art-v11[data-cat-open="true"] .v11-fade {
  opacity: 0;
  transform: translateY(-12px);
  pointer-events: none;
}

/* When a category is open, the index gets a paper backdrop so the
   drawer items have a solid surface to sit on — nothing bleeds
   through from behind. Driven by the same data-cat-open hook. */
.art-v11 .v11-index-pad {
  transition: background var(--cat-dur) var(--cat-ease),
              box-shadow var(--cat-dur) var(--cat-ease),
              padding var(--cat-dur) var(--cat-ease);
  padding: 0;
  background: transparent;
}
.art-v11[data-cat-open="true"] .v11-index-pad {
  background: var(--bg-paper);
  padding: 28px 24px 0;
  margin: 0 -24px;
  box-shadow: 0 -28px 32px -16px var(--bg-paper);
}
