/* ==========================================================================
   Kenneth Dulfo — Portfolio
   Design tokens extracted from the original Figma Make build.
   Edit tokens here; everything else cascades.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  --bg:                 #ffffff;
  --fg:                 oklch(0.145 0 0);
  --card:               #ffffff;
  --card-fg:            oklch(0.145 0 0);
  --muted:              #ececf0;
  --muted-fg:           #717182;
  --primary:            #030213;
  --primary-fg:         #ffffff;
  --secondary:          oklch(0.95 0.0058 264.53);
  --secondary-fg:       #030213;
  --accent:             #e9ebef;
  --accent-fg:          #030213;
  --border:             rgba(0, 0, 0, 0.10);
  --input-bg:           #f3f3f5;
  --ring:               oklch(0.708 0 0);
  --destructive:        #d4183d;


  /* Accent tiles / chart ramp — these flip in dark mode */
  --accent-1:           oklch(0.646 0.222 41.116);   /* orange */
  --accent-2:           oklch(0.600 0.118 184.704);  /* teal   */
  --accent-3:           oklch(0.398 0.070 227.392);  /* deep blue */

  /* Case-study result figures. Its own token rather than --accent-1, so the
     hero tiles stay orange and this stays red in both themes. */
  --result:             oklch(0.577 0.245 27.325);  /* red */

  /* Storyboard beat numbers. --accent-2 straight is 3.3:1 on the page, which is
     under AA for a 12px label, so the light value is taken down a step. */
  --beat:               oklch(0.470 0.100 184.704);  /* darker teal */

  /* Unlit words in the pinned statement. Deliberately low contrast — it is
     text that has not been read yet, not text anyone has to read. */
  --say-dim:            color-mix(in oklch, var(--fg) 16%, transparent);

  /* Typography */
  --font-display: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-ui:      'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --text-xs:   12px;
  --text-sm:   14px;
  --text-base: 16px;
  --text-lg:   18px;
  --text-xl:   20px;
  --text-2xl:  24px;
  --text-3xl:  30px;
  --text-4xl:  36px;
  --text-6xl:  60px;

  /* Spacing & shape */
  --radius:      10px;
  --radius-sm:   8px;
  --radius-lg:   14px;
  --radius-full: 9999px;

  --content-max:  1000px;

  /* Dock height plus the gap above and below it. Anything that has to clear
     the dock measures from here. */
  --dock-h:       52px;
  --dock-clear:   108px;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.05);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

.dark {
  --bg:                 oklch(0.145 0 0);
  --fg:                 oklch(0.985 0 0);
  --card:               oklch(0.145 0 0);
  --card-fg:            oklch(0.985 0 0);
  --muted:              oklch(0.269 0 0);
  --muted-fg:           oklch(0.708 0 0);
  --primary:            oklch(0.985 0 0);
  --primary-fg:         oklch(0.205 0 0);
  --secondary:          oklch(0.269 0 0);
  --secondary-fg:       oklch(0.985 0 0);
  --accent:             oklch(0.269 0 0);
  --accent-fg:          oklch(0.985 0 0);
  --border:             oklch(0.269 0 0);
  --input-bg:           oklch(0.269 0 0);
  --ring:               oklch(0.439 0 0);
  --destructive:        oklch(0.396 0.141 25.723);


  --accent-1:           oklch(0.488 0.243 264.376); /* blue    */
  --accent-2:           oklch(0.696 0.170 162.480); /* emerald */
  --accent-3:           oklch(0.769 0.188 70.080);  /* amber   */

  /* Same red, lifted so it still reads against the dark surface */
  --result:             oklch(0.704 0.191 22.216);

  /* The emerald already carries on a near-black ground */
  --beat:               oklch(0.696 0.170 162.480);

  /* A touch stronger: 16%% of white on near-black disappears entirely. */
  --say-dim:            color-mix(in oklch, var(--fg) 22%, transparent);

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6);
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease);
}

h1, h2, h3, h4 { margin: 0; font-weight: 400; }
p  { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img { max-width: 100%; display: block; }
a  { color: inherit; text-decoration: none; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

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

/* All inline icons are stroke-based line icons */
.ic {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Brand marks (Simple Icons) are solid glyphs, not strokes */
.brand {
  fill: currentColor;
  stroke: none;
  flex: none;
}

/* Multi-colour mark — each path carries its own brand fill, so only make
   sure nothing inherits a stroke onto it. */
.figma-mark {
  stroke: none;
  flex: none;
}

/* Brand marks in their own colours. .brand fills with currentColor, so setting
   `color` is what tints them. Values are the official brand hexes, except where
   noted — a few are lifted in dark mode, where the real colour goes muddy
   against a dark chip. */
.brand--sketch { color: #F7B500; }
.brand--adobe  { color: #FF0000; }
.brand--jira   { color: #0052CC; }
/* Miro is the one brand that has to flip: its registered hex is near-black
   (#050038), which is right on a light chip, while the yellow it is better
   known for only reads once the chip goes dark. Both are genuine Miro colours. */
.brand--miro   { color: #050038; }

.dark .brand--adobe { color: #FF6B6B; }
.dark .brand--jira  { color: #4C9AFF; }
.dark .brand--miro  { color: #FFD02F; }

/* Raster marks — product and certification-body logos that have no clean vector
   source. Capped by height so every chip keeps one baseline. The white plate is
   the same trick the timeline logos use: NN/g is half black and Google's G has a
   white ground, and neither survives a dark chip without it. */
.chip__logo {
  flex: none;
  display: block;
  height: 13px;
  width: auto;
  max-width: 46px;
  object-fit: contain;
  background: #fff;
  border-radius: 3px;
  padding: 1px;
}

/* --------------------------------------------------------------------------
   3. Layout shell
   -------------------------------------------------------------------------- */
.app { min-height: 100vh; }

.main { min-width: 0; }

.container {
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 40px;
}

/* The dock floats over the page, so an anchor jump would otherwise land a
   section's heading underneath it. */
.section { padding: 96px 0; scroll-margin-top: var(--dock-clear); }

.section--hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  /* Extra at the top so the centred hero clears the dock rather than sitting
     under it on a short screen. */
  padding: var(--dock-clear) 0 64px;
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 18px;
  border-radius: var(--radius-sm);
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease),
              transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--primary {
  background: var(--primary);
  color: var(--primary-fg);
}
.btn--primary:hover {
  background: color-mix(in oklab, var(--primary) 88%, var(--bg));
  box-shadow: var(--shadow-md);
}

.btn--outline {
  background: var(--card);
  color: var(--fg);
  border: 1px solid var(--border);
}
.btn--outline:hover { background: var(--accent); }

.btn--block { width: 100%; height: 36px; }

.btn .arrow { transition: transform 0.2s var(--ease); }
.btn:hover .arrow { transform: translateX(3px); }

/* --------------------------------------------------------------------------
   6. Typography helpers
   -------------------------------------------------------------------------- */
.eyebrow {
  display: inline-block;
  margin-bottom: 12px;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted-fg);
}

.h-section {
  font-size: var(--text-4xl);
  line-height: 1.12;
  letter-spacing: -0.02em;
  max-width: 22ch;
}

.lede {
  margin-top: 20px;
  max-width: 62ch;
  font-size: var(--text-base);
  line-height: 1.7;
  color: var(--muted-fg);
}

/* --------------------------------------------------------------------------
   8. Cards, stats, grids
   -------------------------------------------------------------------------- */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.bullets { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.bullets li {
  position: relative;
  padding-left: 18px;
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--muted-fg);
}
.bullets li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 9px;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--muted-fg);
  opacity: 0.6;
}

.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--radius-full);
  background: var(--secondary);
  color: var(--secondary-fg);
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 500;
}

.project__desc {
  margin-top: 8px;
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--muted-fg);
}
/* --------------------------------------------------------------------------
   11b. Contact form
   -------------------------------------------------------------------------- */
.form {
  margin-top: 40px;
  max-width: 620px;
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.field { display: flex; flex-direction: column; gap: 8px; }

.field label {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 500;
}

.field input,
.field textarea {
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--input-bg);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.55;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.field textarea { resize: vertical; min-height: 150px; }

.field input::placeholder,
.field textarea::placeholder { color: var(--muted-fg); }

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: color-mix(in oklab, var(--fg) 45%, transparent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--fg) 10%, transparent);
}

/* Only flag invalid once something has actually been typed */
.field input:not(:placeholder-shown):invalid,
.field textarea:not(:placeholder-shown):invalid { border-color: var(--destructive); }

/* Spam trap — off-screen rather than display:none, which some bots detect */
.form__honey {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.form__foot {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.form__status {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--muted-fg);
}
.form__status.is-ok    { color: var(--accent-2); }
.form__status.is-error { color: var(--destructive); }

.form__hint {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--muted-fg);
}

.form.is-sending { opacity: 0.65; pointer-events: none; }

.link { text-decoration: underline; text-underline-offset: 2px; }

/* --------------------------------------------------------------------------
   12. Case study view
   -------------------------------------------------------------------------- */
.view[hidden] { display: none !important; }

/* Clears the floating dock, like every other top-of-page block. */
.cs__head { padding: var(--dock-clear) 0 0; }
.cs__title {
  margin-top: 18px;
  font-size: var(--text-4xl);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.025em;
}
.cs__sub {
  margin-top: 14px;
  max-width: 60ch;
  font-size: var(--text-lg);
  color: var(--muted-fg);
}

/* Content-sized blocks, not equal columns — the values differ wildly in
   length, so 1fr starved "Team" while wasting space on "Duration". */
.cs__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 24px 56px;
  margin-top: 40px;
  padding: 24px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.cs__meta dt {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted-fg);
}
.cs__meta dd { margin: 6px 0 0; font-size: var(--text-sm); font-weight: 500; }

.cs__hero-img {
  margin-top: 48px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  overflow: hidden;
  max-height: 460px;
}
.cs__hero-img img { width: 100%; object-fit: cover; object-position: top center; }

/* Stand-in until real case-study media exists */
.cs__hero-img--placeholder {
  display: grid;
  place-items: center;
  min-height: 280px;
  color: var(--muted-fg);
  background: var(--input-bg);
}

/* Frame exports are painted as background images rather than <img> so a file
   that isn't there yet degrades to a plain panel instead of a broken icon. */
.cs__hero-shot {
  max-height: none;
  aspect-ratio: 1600 / 960;
  background-color: var(--input-bg);
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

.showcase-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 24px;
}

.shot { margin: 0; }

.shot__img {
  cursor: zoom-in;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background-color: var(--input-bg);
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

.shot__img:hover {
  border-color: var(--ring);
  box-shadow: var(--shadow-md);
}

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

/* Tall or small frames letterbox instead of cropping to fill */
.shot__img--contain { background-size: contain; }

/* Very tall screens: crop to the top at full width, rather than shrinking the
   whole scroll into a sliver nobody can read */
.shot__img--top {
  background-size: cover;
  background-position: top center;
}

/* Service blueprints are five times wider than they are tall. Neither the 16/10
   box nor --top can hold one: cover scales by height and throws most of the
   width away. Matching the tile to the artwork keeps all three lanes in frame,
   and the lightbox is where the labels get read. */
.shot__img--wide {
  aspect-ratio: 3738 / 750;
  background-size: cover;
  background-position: center;
}

/* Phone screens, sat on the panel at a readable size. Neither cover nor contain
   has a middle setting — cover always scales by width, so it shows the same
   sliver of design however the source is cropped. Sizing to a share of the tile
   is the actual dial: lower shows more of the screen, higher shows less.
   48% puts roughly the top 510px of a 393px-wide screen in view. */
.shot__img--screen {
  background-size: 48% auto;
  background-position: top center;
}

.shot figcaption {
  margin-top: 10px;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--muted-fg);
}

/* ---- Figma embed, loaded on demand --------------------------------------- */
.figma-embed {
  margin-top: 24px;
  aspect-ratio: 16 / 10;
  min-height: 340px;
  /* With no width set, the ratio turned min-height into a 544px width and
     pushed every phone page sideways. A set width wins: taller, never wider. */
  width: 100%;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  overflow: hidden;
  background: var(--input-bg);
}

.figma-embed__poster {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 32px;
  text-align: center;
  background: var(--input-bg) center / cover no-repeat;
}
/* Each file sits behind its own project's cover (EastWest sets its own inline) */
#view-segstream .figma-embed__poster { background-image: url('../assets/segstream-cover.jpg?v=6'); }
#view-entrego .figma-embed__poster   { background-image: url('../assets/entrego-cover.jpg?v=6'); }
#view-kwik .figma-embed__poster      { background-image: url('../assets/kwik-cover.jpg?v=6'); }
#view-aia .figma-embed__poster       { background-image: url('../assets/aia-cover.jpg?v=6'); }

/* Scrim so the label stays readable over the cover art */
.figma-embed__poster::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(10, 10, 12, 0.62);
  transition: background-color 0.25s var(--ease);
}
.figma-embed__poster:hover::before { background: rgba(10, 10, 12, 0.54); }
.figma-embed__poster > * { position: relative; }

.figma-embed__badge {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-bottom: 4px;
  border-radius: var(--radius-lg);
  background: #fff;
  box-shadow: var(--shadow-lg);
  transition: transform 0.25s var(--ease);
}
.figma-embed__poster:hover .figma-embed__badge { transform: translateY(-4px) scale(1.04); }

.figma-embed__label {
  font-size: var(--text-lg);
  font-weight: 500;
  color: #fff;
}

.figma-embed__hint {
  max-width: 42ch;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.74);
}

.figma-embed iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.figma-embed__aside {
  margin-top: 12px;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--muted-fg);
}

/* Video hero: show the whole 16:9 frame rather than cropping it */
.cs__hero-img--video { max-height: none; background: #000; line-height: 0; }

/* A phone recording has no business stretching to the full column — hold it at
   a handset width and let the dark field carry the rest. */
.brew-demo {
  display: grid;
  place-items: center;
  padding: 30px 0;
  background: #0E0E10;
  line-height: 0;
}
.cs__hero-img--video.brew-demo video {
  width: 292px;
  max-width: 68%;
  height: auto;
  aspect-ratio: auto;
  border-radius: 20px;
}

/* Caption for media that sits outside a <figure> */
/* Obvious placeholders — dashed, labelled, and clearly not a broken image,
   so an unfinished section still reads as deliberate. */
.ph-shot {
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  border: 1px dashed var(--border);
  background: var(--input-bg);
  display: grid;
  place-items: center;
  text-align: center;
  padding: 20px;
}
.ph-shot span {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--muted-fg);
  max-width: 34ch;
}
.ph-shot--wide { aspect-ratio: 16 / 7; margin-top: 24px; }

/* Establishing shot. The photo is portrait, so it sits beside the copy rather
   than being cropped into a band — a 16:7 crop lost the sign or the door, and
   those are the two things that make it read as this cafe. */
.cafe-open {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 32px;
  align-items: center;
  margin-top: 24px;
}
.cafe-open__photo {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}
.cafe-open__text p + p { margin-top: 16px; }
@media (max-width: 760px) {
  .cafe-open { grid-template-columns: 1fr; gap: 18px; }
  .cafe-open__photo { max-width: 320px; }
}

/* Alternating rows: the photo carries real weight at 46% of the column, and
   swapping sides on the second one stops two rows reading as a table. */
.personas {
  display: flex;
  flex-direction: column;
  gap: 28px;
  margin-top: 28px;
}
.persona {
  display: grid;
  grid-template-columns: 46% 1fr;
  gap: 32px;
  align-items: center;
  margin: 0;
}
/* Flipping with order alone would leave the photo in the 1fr column and make
   the two rows different sizes, so the template swaps too. */
.persona--flip { grid-template-columns: 1fr 46%; }
.persona--flip .persona__photo { order: 2; }

.persona__photo { aspect-ratio: 4 / 3; }
/* The filled version is an <img>, so it needs the sizing the placeholder got
   from being a grid container. */
img.persona__photo {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}
.persona__photo span { max-width: 26ch; }

.persona__body h3 { font-size: var(--text-xl); font-weight: 500; letter-spacing: -0.01em; }
.persona__role {
  margin-top: 4px;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-fg);
}
.persona__body > p:last-child {
  margin-top: 12px;
  font-size: var(--text-base);
  line-height: 1.7;
}

@media (max-width: 760px) {
  .persona { grid-template-columns: 1fr; gap: 16px; }
  .persona--flip .persona__photo { order: 0; }
  .persona__photo { aspect-ratio: 16 / 10; }
  .persona__photo span { max-width: 34ch; }
}

/* Testimonial. The rule, not a giant quote glyph — the words should carry it. */
.testimonial {
  margin: 32px 0 0;
  padding: 26px 0 26px 30px;
  border-left: 3px solid var(--result);
}
.testimonial blockquote {
  margin: 0;
  font-size: var(--text-2xl);
  line-height: 1.45;
  letter-spacing: -0.015em;
  font-weight: 400;
  color: var(--fg);
  max-width: 44ch;
}
.testimonial figcaption {
  margin-top: 18px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-ui);
}
.testimonial__who { font-size: var(--text-sm); font-weight: 600; }
.testimonial__role {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-fg);
}
/* Unfilled: dashed and muted, so it reads as pending rather than as a real quote */
.testimonial.is-placeholder { border-left-style: dashed; border-left-color: var(--border); }
.testimonial.is-placeholder blockquote { color: var(--muted-fg); font-size: var(--text-lg); }

.prose .testimonial + p { margin-top: 32px; }

/* .btn sets its own display, which out-ranks the UA [hidden] rule — so hiding
   a button needs saying explicitly. Same trap as .view[hidden]. */
.btn[hidden] { display: none !important; }

/* The one place on a case study that asks for an action, so it gets a panel
   rather than a sentence with a link in it. */
.try-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
  margin-top: 26px;
  padding: 30px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--secondary);
}
.try-cta__text { flex: 1 1 340px; min-width: 0; }
.try-cta__text h3 {
  font-size: var(--text-xl);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin-bottom: 8px;
}
.try-cta__text p {
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--muted-fg);
  max-width: 54ch;
}
.try-cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  flex: 0 0 auto;
}
@media (max-width: 720px) {
  .try-cta { padding: 24px; gap: 20px; }
  .try-cta__actions { width: 100%; }
  .try-cta__actions .btn { flex: 1 1 100%; justify-content: center; }
}

/* Who did what on an AI-built project — three parallel claims, so they read
   better side by side than stacked as bullets. */
.credits {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 26px;
}
.credit {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.credit__tag {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--result);
}
.credit p {
  font-size: var(--text-sm);
  line-height: 1.65;
  color: var(--muted-fg);
}
@media (max-width: 860px) { .credits { grid-template-columns: 1fr; } }

/* Blocks inside a section had nothing between them and the paragraph that
   follows, so copy ran straight into the thing above it. */
.prose .cafe-open + p,
.prose .credits + p,
.prose .personas + p,
.prose .results + p,
.prose .showcase-grid + p,
.prose .ph-shot + p,
.prose .steps + p,
.prose .rail + p,
.prose .cs__hero-img + p { margin-top: 28px; }

/* A storyboard runs heading, strip, heading, strip. Without these the next
   story sits on top of whatever came before it. */
.prose .rail + .rail { margin-top: 48px; }
.prose figure + .rail { margin-top: 40px; }
.prose p + .rail { margin-top: 32px; }

/* Lead-ins inside a bullet, so the point lands before the explanation */
.bullets li strong { color: var(--fg); font-weight: 500; }

.cap-note {
  margin-top: 12px;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--muted-fg);
}
.cs__hero-img--video video {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  display: block;
}
/* The demo films are cut at the covers' 5:3 */
.cs__hero-img--demo video { aspect-ratio: 5 / 3; }

.prose { margin-top: 56px; }
.prose h2 {
  font-size: var(--text-2xl);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin-bottom: 14px;
}
.prose h3 {
  font-size: var(--text-lg);
  font-weight: 500;
  margin-bottom: 10px;
}
/* Runs the full container width, matching the bullet lists beside it — the
   measure was the only thing making body copy and bullets disagree. */
.prose p {
  line-height: 1.75;
  color: var(--muted-fg);
}
/* Sections used to hold a single paragraph each, so nothing ever separated two
   in a row. They run together the moment one does. */
.prose p + p,
.prose figure + p,
.prose p + figure,
.prose ul + p { margin-top: 18px; }
.prose > * + * { margin-top: 40px; }

.steps { display: flex; flex-direction: column; gap: 32px; margin-top: 24px; }
.step { display: grid; grid-template-columns: 44px 1fr; gap: 20px; }
.step__num {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-full);
  background: var(--secondary);
  color: var(--secondary-fg);
  font-weight: 600;
}

/* ---------------------------------------------------------- Storyboard */

/* Every panel came out of Figma with its caption welded to the bottom of the
   artwork. Those bars are cropped off in the export and the words live here as
   real text — otherwise each beat said the same thing twice, the second time at
   about nine pixels. The role tag stays in the artwork: it is the storyboard's
   own way of saying whose turn this is. */

.rail__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
}

.rail__head h3 { margin-bottom: 0; }

.rail__nav { display: flex; gap: 8px; flex: none; }

.rail__btn {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--card);
  color: var(--fg);
  cursor: pointer;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease),
              opacity 0.2s var(--ease);
}

.rail__btn:hover:not(:disabled) { background: var(--secondary); border-color: var(--ring); }
.rail__btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.rail__btn:disabled { opacity: 0.35; cursor: default; }
.rail__btn .ic { width: 18px; height: 18px; }

/* Nothing to page through until the strip is wider than the column. */
.rail:not(.is-scrollable) .rail__nav { display: none; }

.rail__lead { margin-top: 14px; }

/* A storyboard is read along, not down. Nine beats stacked made the section
   four screens tall; sideways it is one, and the clipped third card is the
   thing that says there is more. */
.sb {
  list-style: none;
  margin: 18px 0 0;
  padding: 0 0 14px;          /* room for the scrollbar under the cards */
  display: flex;
  gap: 20px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  counter-reset: beat;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.sb:focus-visible { outline: 2px solid var(--ring); outline-offset: 4px; }

.sb::-webkit-scrollbar { height: 8px; }
.sb::-webkit-scrollbar-track { background: transparent; }
.sb::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: var(--radius-full);
}
.sb::-webkit-scrollbar-thumb:hover { background: var(--ring); }

.sb__beat {
  counter-increment: beat;
  flex: 0 0 min(420px, 86%);
  scroll-snap-align: start;
}

/* The export is cropped to 2:1, so the tile is too and nothing is cut away. */
.sb__beat .shot__img {
  aspect-ratio: 2 / 1;
  /* Anchored at the corner the role tag lives in. The art overscans as the
     strip crosses the screen, and anchoring here means it grows down and to
     the left — away from the tag, which sits flush against the top edge with
     no headroom at all. Sliding the art instead, the way the photographs do,
     would cut the tag on every panel. */
  background-position: right top;
}

.sb__kicker {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 14px 0 6px;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 500;
  line-height: 1.4;
}

.sb__kicker::before {
  content: counter(beat, decimal-leading-zero);
  flex: none;
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--beat);
}

.sb__beat p {
  font-size: var(--text-sm);
  line-height: 1.7;
  color: var(--muted-fg);
}

/* Three portraits and their name plates — a taller frame than the beats. */
.shot__img--cast { aspect-ratio: 880 / 572; }

@media (prefers-reduced-motion: reduce) {
  .sb { scroll-snap-type: none; }
}

.results { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 24px; }
.result { text-align: center; padding: 28px 20px; }
.result__num {
  font-size: var(--text-4xl);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--result);
  /* Word values like "In-App" would otherwise break at the hyphen */
  white-space: nowrap;
  /* Counts up like the About stats; fixed-width digits keep it from twitching */
  font-variant-numeric: tabular-nums;
}
.result__label { margin-top: 6px; font-weight: 500; }
.result__desc  { margin-top: 4px; font-size: var(--text-sm); color: var(--muted-fg); }

.next-project {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin: 72px 0 clamp(96px, 10vw, 144px);
  padding: 32px;
  flex-wrap: wrap;
  text-align: left;
  width: 100%;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.next-project:hover {
  border-color: color-mix(in oklab, var(--fg) 22%, transparent);
  box-shadow: var(--shadow-md);
}
.next-project__title { font-size: var(--text-2xl); font-weight: 500; letter-spacing: -0.02em; }
/* --------------------------------------------------------------------------
   13. Scroll reveal
   -------------------------------------------------------------------------- */
/* The stagger is not written into the markup. main.js sets --i on each element
   at the moment it is released, counting only the ones arriving in the same
   frame, so an element that enters the screen on its own waits for nothing.
   Hand-written delays could not do that: a fixed 0.48s was still 0.48s when
   the element was the only thing on screen. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
  transition-delay: calc(var(--i, 0) * 55ms);
}
.reveal.is-visible { opacity: 1; transform: none; }

/* Blocks — cards, figures, media, anything with edges. A 14px rise on a small
   card overshoots and reads as flying in, so they travel less and settle out
   of a slight scale instead. */
.reveal--block { transform: translateY(8px) scale(0.985); }
.reveal--block.is-visible { transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  /* the lightbox still opens, it just appears instead of zooming */
  .lightbox, .lightbox__img { transition: none; }
  .shot__img { transition: none; }
}

/* --------------------------------------------------------------------------
   13b. Lightbox
   -------------------------------------------------------------------------- */

/* Sits above the dock and the theme switch, both of which are 60. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 56px 72px;
  background: rgba(0, 0, 0, 0.88);
  opacity: 0;
  transition: opacity 0.26s var(--ease);
}

.lightbox.is-open { opacity: 1; }

/* .lightbox's display:flex would otherwise beat the reset's [hidden] rule */
.lightbox[hidden] { display: none !important; }

.lightbox__stage {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  max-width: 100%;
  max-height: 100%;
}

.lightbox__img {
  display: block;
  max-width: 100%;
  /* leave room for the caption under it. dvh so a phone's retracting address
     bar doesn't push the caption off the screen; vh is the fallback. */
  max-height: calc(100vh - 170px);
  max-height: calc(100dvh - 170px);
  width: auto;
  height: auto;
  border-radius: var(--radius);
  background: var(--input-bg);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  /* the open/close zoom is driven from JS, which sets this each time */
  transform-origin: top left;
}

.lightbox__cap {
  max-width: 68ch;
  text-align: center;
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.72);
}

/* A long caption would otherwise push the picture off a short screen */
.lightbox__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.lightbox__count {
  display: block;
  margin-bottom: 4px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.45);
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.lightbox__btn:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.35);
}

.lightbox__btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lightbox__btn .ic { width: 20px; height: 20px; }

.lightbox__close { top: 20px; right: 20px; }
.lightbox__prev  { left: 16px;  top: 50%; transform: translateY(-50%); }
.lightbox__next  { right: 16px; top: 50%; transform: translateY(-50%); }

/* A single-image section has nothing to page through */
.lightbox.is-solo .lightbox__prev,
.lightbox.is-solo .lightbox__next { display: none; }

@media (max-width: 720px) {
  .lightbox { padding: 64px 16px 24px; }
  .lightbox__prev  { left: 8px; }
  .lightbox__next  { right: 8px; }
  .lightbox__img   { max-height: calc(100vh - 210px); max-height: calc(100dvh - 210px); }
}

/* --------------------------------------------------------------------------
   14. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 820px) {
  :root { --text-6xl: 40px; --text-4xl: 28px; }

  .container { padding: 0 24px; }
  .section { padding: 72px 0; }

  .grid-3, .results { grid-template-columns: 1fr; }
  .showcase-grid { grid-template-columns: 1fr; }
  .cs__meta { gap: 20px 32px; }
  .step { grid-template-columns: 1fr; gap: 12px; }
  .next-project { margin-bottom: 72px; }
}
