/* Feature Store Summit theme, after the summit's Google Slides template: Inter, summit red, the red pixel grid, "Organized by Hopsworks".
   Red is the brand accent (titles, slash, kicker, bullets, progress bar); green keeps its meaning in diagrams (flows, feature views, right).
   The deck names its event once, for the lockup: :root { --event: 'FEATURE STORE\A SUMMIT 2026'; } */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;800&display=swap');

/* the accent is a registered colour so a slide can animate it (.slide.rondb) */
@property --accent { syntax: '<color>'; inherits: true; initial-value: #e11d2e; }

:root {
  --ink: #0a0a0a;
  --ink-2: #5c5c5c;
  --mute: #9a9a9a;
  --line: #e4e4e4;
  --paper: #ffffff;
  --wash: #f6f6f4;
  --summit: #e11d2e;
  --accent: var(--summit);
  --accent-deep: #a8121e;
  --accent-wash: #fbe3e5;
  --green: #1eb182;
  --green-deep: #188f69;
  --green-wash: #e6f2ee;
  --blue: #2885a4;
  --orange: #ffab40;
  --orange-ink: #c77a12;
  --band: #faf6ef;
  --red: #eb5757;
  --red-ink: #c44d4d;
  --red-wash: #fdecec;
  /* dark surfaces: code, terminals */
  --dark: #111111;
  --dark-2: #1c1c1c;
  --dark-edge: #000000;
  --dark-line: #3a3a3a;
  --dark-fg: #ededed;
  --dark-out: #bdbdbd;
  --dark-mute: #8a8a8a;
  --dark-green: #65d3af;
  --dark-orange: #f2c38a;
  --dark-red: #ff8a8a;
  --dark-blue: #9fd3ff;
  /* the pixel grid: squares from cold to hot, and the gap between them */
  --px-cold: #0b0302;
  --px-hot: #6b2720;
  --px-gap: #000000;
  /* RonDB yellow (rondb-web logo), for slides that pass through RonDB's colour: .slide.rondb */
  --rondb: #ffdf00;
  /* deck chrome: the stage around the slide, HUD and overview outlines */
  --stage: #0a0a0a;
  --stage-line: #3a3a3a;
  --lift: 0 6px 10px rgb(0 0 0 / .12);
  --sans: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
  --event: '';
}

/* ---------- layouts ----------
   default  content slide: pixel strip on top with the lockup, red rule at the foot, "Organized by" bottom right
   .band    pixel band on the left, lockup at its foot (title, up next)
   .grid    full pixel field, white type (hero, sections, breaks, partners)
   .bare    no chrome
   .still   the default chrome with its pixels held still (text slides)
   Pseudo-elements carry all of it: .slide::before the pixels, .slide::after the lockup, .in::before the rule, .in::after the mark. */
.slide::before {
  content: ''; position: absolute; left: 0; top: 0; right: 0; height: 7.2%;
  --pitch: calc(var(--u) * 2.9);
  background-color: var(--px-gap);
  background-image:
    linear-gradient(90deg, var(--px-gap) 0 40%, transparent 40%),
    linear-gradient(var(--px-gap) 0 40%, transparent 40%),
    linear-gradient(var(--px-dir, 90deg), var(--px-cold), var(--px-hot));
  background-size: var(--pitch) var(--pitch), var(--pitch) var(--pitch), 100% 100%;
}
/* pixels.js draws the same grid live on a canvas in the same place, and the CSS grid steps aside */
.slide > canvas.px { position: absolute; left: 0; top: 0; width: 100%; height: 7.2%; display: block; }
.slide.band > canvas.px { width: 25%; height: 100%; }
.slide.grid > canvas.px { height: 100%; }
.slide.px-live::before { visibility: hidden; }

.slide::after {
  content: var(--event); white-space: pre; position: absolute; z-index: 2;
  left: calc(var(--u) * 2.2); top: 3.6%; transform: translateY(-50%);
  padding-left: calc(var(--u) * 7.2);
  background: url('fss-mark.svg') no-repeat left center / calc(var(--u) * 6.2) auto;
  font: 700 calc(var(--u) * 1.1)/1.05 var(--sans); color: var(--accent);
}
.slide > .in { padding-top: calc(7.2% + var(--u) * 4.5); padding-bottom: calc(var(--u) * 7); }
.slide.wide > .in { padding-top: calc(var(--u) * 6.5); }
.slide > .in::before { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: calc(var(--u) * .55); background: var(--accent); }
.slide > .in::after {
  content: 'Organized by'; position: absolute; right: calc(var(--u) * 2.4); bottom: calc(var(--u) * 2.2);
  padding-right: calc(var(--u) * 11.4); line-height: calc(var(--u) * 2.4);
  background: url('hopsworks.svg') no-repeat right center / auto calc(var(--u) * 2.3);
  font: 500 calc(var(--u) * 1.3) var(--sans); color: var(--ink);
}

.slide.band::before { right: auto; width: 25%; height: 100%; --px-dir: 0deg; --pitch: calc(var(--u) * 3.6); }
.slide.band::after, .slide.grid::after {
  top: auto; bottom: calc(var(--u) * 2.4); transform: none;
  font-size: calc(var(--u) * 1.35); padding-left: calc(var(--u) * 8.2); background-size: calc(var(--u) * 7.2) auto;
}
.slide.band > .in { padding: calc(var(--u) * 6) calc(var(--u) * 6) calc(var(--u) * 8) calc(25% + var(--u) * 7.5); justify-content: center; }
.slide.band > .in::before { display: none; }
.slide.band > .in::after { right: auto; left: calc(25% + var(--u) * 7.5); }

.slide.grid { background: var(--px-gap); color: var(--paper); }
.slide.grid::before { height: 100%; --pitch: calc(var(--u) * 3.6); }
.slide.grid::after { color: var(--paper); }
.slide.grid > .in { padding-top: calc(var(--u) * 5); padding-bottom: calc(var(--u) * 7); }
.slide.grid > .in::before { display: none; }
.slide.grid > .in::after { color: var(--paper); background-image: url('hopsworks-white.svg'); }

/* a pass of another colour: the slide arrives in its --pass colour (.slide.rondb in RonDB yellow, .slide.pass with
   style="--pass: var(--green)" in that token); pixels.js sends a second scan front 2.6 s in that turns the grid back to summit red, and the
   accent of the text flips as that front reaches it. Doc mode, print, overview and reduced motion stay red. */
.slide.rondb { --pass: var(--rondb); }
html.deck .slide:is(.rondb, .pass).here { animation: accent-pass 4.6s linear both; }
@keyframes accent-pass { 0%, 60% { --accent: var(--pass); } 68%, 100% { --accent: var(--summit); } }

.slide.bare::before, .slide.bare::after, .slide.bare > .in::before, .slide.bare > .in::after { display: none; }

/* ---------- type ---------- */
h1, h2 { letter-spacing: -.02em; }
h2 { font-size: calc(var(--u) * 2.8); font-weight: 700; max-width: none; }
.kicker { font-family: var(--sans); font-size: calc(var(--u) * 1.4); letter-spacing: .03em; color: var(--ink-2); margin-bottom: calc(var(--u) * 1.2); }
/* the template's red title: a bold line, then the rest in regular */
.title-red { color: var(--accent); font-size: calc(var(--u) * 3); line-height: 1.22; font-weight: 400; letter-spacing: -.01em; max-width: 32ch; }
.title-red b { font-weight: 700; display: block; }
.slide.grid h1 { font-size: calc(var(--u) * 5.6); font-weight: 800; text-transform: uppercase; letter-spacing: -.01em; }
.accent { color: var(--accent); }

/* ---------- speaker: framed photo with the red bar, name, role ---------- */
.speakers { display: flex; gap: calc(var(--u) * 5); margin-top: calc(var(--u) * 3); }
.speaker { display: grid; grid-template-columns: auto 1fr; gap: calc(var(--u) * 1.8); align-items: start; }
.speaker .ph {
  position: relative; width: calc(var(--u) * 10.5); aspect-ratio: 1; padding: calc(var(--u) * .35); border: 1px solid var(--ink); background: var(--paper);
}
.speaker .ph::after { content: ''; position: absolute; left: -1px; right: -1px; bottom: calc(var(--u) * -.75); height: calc(var(--u) * .5); background: var(--accent); transform-origin: left; }
.speaker .ph img { width: 100%; height: 100%; object-fit: cover; display: block; border: 0; filter: grayscale(1); }
.speaker .ph > span { display: block; width: 100%; height: 100%; overflow: hidden; }
.speaker .ph.none { display: grid; place-items: center; font: 700 calc(var(--u) * 3) var(--sans); color: var(--mute); }
.speaker > div { min-width: calc(var(--u) * 17); }
.speaker b { display: block; font-size: calc(var(--u) * 1.9); margin-top: calc(var(--u) * 1); }
.speaker span { display: block; font-size: calc(var(--u) * 1.5); color: var(--ink-2); line-height: 1.35; max-width: 26ch; }

/* ---------- agenda legend ---------- */
.legend svg { width: 1.2em; height: 1.2em; stroke: var(--accent); vertical-align: -.22em; }
.legend { display: flex; gap: calc(var(--u) * 2); font-size: calc(var(--u) * 1.15); color: var(--ink-2); }
.legend svg { margin-right: .3em; }

/* ---------- monogram: <div class="mono-logo"><svg> one <rect> per square </svg><span class="name"> ----------
   with .assemble, the squares grow in place along the diagonal (--i = column + row), white settling into red, and the name's tracking tightens as its words land */
.mono-logo { display: flex; align-items: center; gap: calc(var(--u) * 2.2); }
.mono-logo svg { height: var(--logo-h, calc(var(--u) * 6)); width: auto; fill: var(--accent); overflow: visible; }
.mono-logo > .name { font: 800 calc(var(--logo-h, calc(var(--u) * 6)) * .36)/1.02 var(--sans); text-transform: uppercase; color: var(--accent); }

/* ---------- motion ----------
   one curve (expo out) and one clock: a piece's delay is --i steps of 70ms, words inside it add --w steps of 40ms.
   .sw > span  a word rising from behind its own mask (headlines)
   .rise       a block settling in from a short drop and a blur, back to its --rest-filter if it has one
   .ph         a photo opening upward, the image easing back from a slight zoom, its red bar drawing from the left
   Deck mode only; doc mode, print, overview and reduced motion show every end state. */
:root { --expo: cubic-bezier(.16, 1, .3, 1); }
.sw { display: inline-block; overflow: clip; vertical-align: bottom; padding-bottom: .12em; margin-bottom: -.12em; }
.sw > span { display: inline-block; }
html.deck .slide .sw > span { transform: translateY(110%); }
html.deck .slide.here .sw > span { animation: up .9s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + var(--w, 0) * 40ms + 120ms); }
@keyframes up { from { transform: translateY(110%); } to { transform: none; } }

html.deck .slide .rise { opacity: 0; }
html.deck .slide.here .rise { animation: rise 1s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + 120ms); }
@keyframes rise { from { opacity: 0; transform: translateY(calc(var(--u) * 1.2)); filter: blur(6px) var(--rest-filter,); } to { opacity: 1; transform: none; filter: blur(0) var(--rest-filter,); } }

html.deck .slide .ph { clip-path: inset(100% 0 0 0); }
html.deck .slide.here .ph { animation: open 1s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + 160ms); }
html.deck .slide.here .ph img { animation: settle 1.6s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + 160ms); }
html.deck .slide .ph::after { transform: scaleX(0); }
html.deck .slide.here .ph::after { animation: draw .8s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + 520ms); }
@keyframes open { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 -20% 0); } }
@keyframes settle { from { transform: scale(1.18); } to { transform: none; } }
@keyframes draw { from { transform: scaleX(0); } to { transform: none; } }

html.deck .slide .mono-logo.assemble rect { transform-box: fill-box; transform-origin: center; transform: scale(0); }
html.deck .slide.here .mono-logo.assemble rect { animation: cell .8s var(--expo) both, tint 1.2s ease-out both; animation-delay: calc(var(--i) * 45ms + 200ms); }
html.deck .slide.here .mono-logo.assemble > .name { animation: track 1.6s var(--expo) both; animation-delay: calc(var(--i, 0) * 70ms + 120ms); }
@keyframes cell { from { transform: scale(0); } to { transform: none; } }
@keyframes tint { from { fill: var(--paper); } to { fill: var(--accent); } }
@keyframes track { from { letter-spacing: .14em; } to { letter-spacing: .01em; } }

@media (prefers-reduced-motion: reduce) {
  html.deck .slide .sw > span, html.deck .slide .rise, html.deck .slide .ph, html.deck .slide .ph::after,
  html.deck .slide .mono-logo.assemble rect { opacity: 1; transform: none; clip-path: none; filter: var(--rest-filter, none); }
}
html.deck.overview .slide :is(.sw > span, .rise, .ph, .ph img, .mono-logo rect), html.deck.overview .slide .ph::after {
  opacity: 1; transform: none; clip-path: none; filter: var(--rest-filter, none); animation: none;
}

/* .deck-link: one link shown on every slide (the summit's link page), a single element in .stage laid over the slide box,
   placed where each layout's chrome leaves room: bottom left on content slides, bottom right on .band (its lockup and
   "Organized by" are on the left), bottom centre on .grid (lockup left, mark right); in doc mode it is a line at the end */
html.deck .stage > .deck-link { position: absolute; width: min(100vw, 177.78vh); aspect-ratio: 16 / 9; container-type: inline-size; pointer-events: none; z-index: 3; margin: 0; }
html.deck .stage > .deck-link a { position: absolute; left: 2.4cqw; bottom: 2.2cqw; line-height: 2.4cqw; font: 500 1.3cqw var(--sans); color: var(--ink-2); border: 0; pointer-events: auto; }
html.deck .stage:has(> .slide.here.band) > .deck-link a { left: auto; right: 2.4cqw; }
html.deck .stage:has(> .slide.here.grid) > .deck-link a { left: 50%; transform: translateX(-50%); color: var(--dark-out); }
html.deck.overview .stage > .deck-link { display: none; }
html:not(.deck) .deck-link { padding: 24px; text-align: center; }
