/* ============================================================================
   Buoy landing page — web port of the SwiftUI BuoyWebsite app.
   Visuals/animations are intended to match the SwiftUI source 1:1.
   1 SwiftUI point == 1 CSS px.
   ========================================================================= */

/* Open Runde — open-source rounded face used in place of SF Pro Rounded (which
   is only reachable via `ui-rounded` on Safari/iOS). Self-hosted so the rounded
   headings render consistently in every browser. Weights: 400/500/600/700. */
@font-face {
  font-family: "Open Runde";
  src: url("/assets/fonts/OpenRunde-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Open Runde";
  src: url("/assets/fonts/OpenRunde-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Open Runde";
  src: url("/assets/fonts/OpenRunde-Semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Open Runde";
  src: url("/assets/fonts/OpenRunde-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* --- Asset-catalog colors (light appearance) ---------------------------
     Defined in display-p3 in the app's asset catalog. The first declaration
     is an sRGB fallback; the color() declaration overrides it on P3-capable
     displays (every modern iPhone). */
  --theme-color: #0086fc;
  --app-background: #eff0f0;

  /* SwiftUI .primary / .secondary in light mode (UIColor.label /
     .secondaryLabel). .primary is pure black; opacities are applied per use. */
  --label: 0 0 0;                   /* rgb triplet for rgb(var(--label) / a) */
  --secondary-label: 60 60 67;      /* alpha 0.6 baked in where used */

  --font-rounded: "Open Runde", ui-rounded, "SF Pro Rounded", -apple-system, system-ui, sans-serif;
  --font-text: -apple-system, "SF Pro Text", system-ui, BlinkMacSystemFont, sans-serif;

  /* Largest iPhone logical width — also the phone-column content cap. */
  --max-phone-width: 440px;

  /* Max width of the centered content column. Below 900px this is the phone
     width (so wider-than-phone viewports show a centered phone column); at
     900px+ it widens and the detail sections lay out horizontally. */
  --content-max: var(--max-phone-width);
}

/* ---- Desktop size class -------------------------------------------------
   At 900px+ the content column widens and the marketing sections go
   horizontal (see .mkt rules below). */
@media (min-width: 900px) {
  :root {
    --content-max: 1100px;
  }
}

@supports (color: color(display-p3 0 0 0)) {
  :root {
    --theme-color: color(display-p3 0 0.52549 0.988235);      /* 0x0086FC */
    --app-background: color(display-p3 0.937255 0.941176 0.941176); /* 0xEFF0F0 */
  }
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Canvas / overscroll (rubber-band) + safe-area background: match the hero and
     footer (appBackground) rather than white. The white page interior is painted
     by `body` below; the hero/footer paint appBackground over it at the ends, so
     the top and bottom safe areas blend seamlessly. */
  background: var(--app-background);
}

body {
  /* appBackground, NOT white: iOS Safari samples the <body> background to tint
     the status-bar / notch safe area when scrolled to the top (theme-color only
     reliably applies to the collapsed toolbar). The white page interior is
     painted by #app below, so the top & bottom safe areas match the hero/footer. */
  background: var(--app-background);
  color: #000000;
  font-family: var(--font-text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body.app-loading {
  overflow: hidden;
}

body.app-loading #app {
  opacity: 0;
  pointer-events: none;
}

/* ---- Root scroll container ----------------------------------------------
   Mirrors ContentView: ZStack { Color.white; ScrollView { VStack(spacing: 32) } } */
#app {
  display: block;
  width: 100%;
  overflow-x: hidden;
  /* The landing page has self-animating demos that mutate DOM above the current
     viewport. Do not let browser scroll anchoring reinterpret those mutations
     as user scroll corrections. */
  overflow-anchor: none;
  /* The white "base" (ContentView's `Color.white`). The hero & footer paint
     appBackground over it; the marketing sections + 32px gaps show this white. */
  background: #ffffff;
}

.page-stack {
  display: flex;
  flex-direction: column;
  gap: 32px;            /* VStack(spacing: 32) */
}

/* Shared text rendering: SwiftUI sizes are in points == px. */
.rounded {
  font-family: var(--font-rounded);
}

/* ============================== Hero ===================================== */
.hero {
  /* Fill the whole screen, including behind the iOS Safari bottom control bar.
     `svh` stops above the bar and `lvh` (the largest viewport unit — stable,
     unlike `dvh`, so the canvas doesn't jitter as the bar shows/hides) only
     reaches the bar's minimized position. No CSS unit reaches past it, and
     `env(safe-area-inset-bottom)` reads 0 while the bar is expanded, so we extend
     `lvh` by a fixed buffer to reach the physical bottom (any excess just scrolls
     off below the fold). `vh`/`lvh` are progressive fallbacks. --hero-bottom-bleed
     is the buffer — adjust if it under/overshoots on a given device. */
  --hero-bottom-bleed: 80px;
  min-height: 100vh;
  min-height: 100lvh;
  min-height: calc(100lvh + var(--hero-bottom-bleed));
  background: var(--app-background);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  position: relative; /* positioning context for the bubble canvas */
}

/* Static message-bubble field behind the hero content. The initial pile starts
   below the Sign Up button, then physics can carry bubbles through the scene. */
.hero-bubbles {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Spacer(minLength: 96) top and bottom -> flexible, >= 96px. The bottom spacer
   grows faster than the top, biasing the text toward the upper part of the hero
   so a larger pool of bubbles can sit below it. */
.hero-spacer {
  flex: 1 1 0;
  min-height: 96px;
}
.hero-spacer--bottom {
  flex-grow: 5;
}

.hero-content {
  width: 100%;
  max-width: 1100px; /* desktop-style content column for all size classes */
  margin-inline: auto;
  padding: 0 32px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  position: relative;
  z-index: 1;
}

.hero-text {
  display: flex;
  flex-direction: column;
  gap: 10px; /* VStack(spacing: 10) */
}

.hero-wordmark {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px; /* HStack(spacing: 10) */
}

.hero-logo {
  width: 46px;
  height: 46px;
  object-fit: contain;
  display: block;
}

.hero-buoy {
  font-size: 28px;
  font-weight: 600;
  line-height: 1;
  color: rgb(var(--label) / 0.5);
  white-space: nowrap;
  position: relative;
  top: 1px; /* .offset(y: 1) */
}

.hero-headline {
  font-size: 38px;
  font-weight: 600;
  line-height: 1.24; /* approx SF line height + lineSpacing(2) */
  color: rgb(var(--label) / 0.7);
  margin: 0;
  letter-spacing: 0.1px;
  max-width: 560px;
}

.hero-signup {
  margin-top: 52px; /* .padding(.top, 52) */
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 10px; /* HStack(spacing: 10) */
  font-size: 22px;
  font-weight: 600;
  color: var(--theme-color);
  text-decoration: none;
  font-family: var(--font-text);
}

.hero-signup svg {
  position: relative;
  top: 1px; /* .offset(y: 2) on the arrow, nudged up 1px to align with the text */
}

/* ========================== Marketing page ============================== */
.mkt {
  padding: 32px 0; /* .padding(.vertical, 32) */
  max-width: var(--content-max); /* centered content column (section is transparent) */
  margin-inline: auto;
}

.mkt-text {
  padding: 0 32px; /* .padding(.horizontal, 32) */
}

.mkt-title {
  font-size: 28px; /* .title */
  font-weight: 600;
  line-height: 1.12;
  color: rgb(var(--label) / 0.7);
  margin: 0;
  letter-spacing: 0.1px;
}

.mkt-body {
  font-size: 20px; /* .title3 */
  font-weight: 600;
  line-height: 1.3;
  color: rgb(var(--label) / 0.4);
  margin: 16px 0 0; /* Spacer().frame(height: 16) */
}

.mkt-graphic {
  padding: 0 32px;
  margin-top: 64px; /* Spacer(48) + default VStack spacing on both sides */
}

.mkt-graphic-inner {
  width: 100%;
  aspect-ratio: var(--mkt-aspect-ratio);
  background: rgb(var(--label) / 0.04); /* Color.primary.opacity(0.04) */
  border-radius: 30px;
  position: relative;
  overflow: hidden;
  /* Keep internal demo reflows/composited layers isolated from page layout. */
  contain: layout paint;
  isolation: isolate;
}

/* ---- Desktop: detail sections become a horizontal text/demo split -------
   Sections alternate sides: odd sections keep text-left/demo-right; even
   sections carry .mkt-media-left to flip (demo-left/text-right). */
@media (min-width: 900px) {
  /* Lay the text + demo out as a single centered group rather than two equal
     halves. With both items at a fixed/intrinsic size (no flex-grow),
     justify-content: center splits the leftover space equally before and after
     the group, so every section has identical leading and trailing padding at
     any width — regardless of the demo being narrower than the text. */
  .mkt {
    display: flex;
    flex-direction: row;
    align-items: center; /* text vertically centered against the demo */
    justify-content: center;
    gap: 64px;
    padding: 32px 32px; /* 32 vertical; 32 is the minimum side padding */
  }

  .mkt-media-left {
    flex-direction: row-reverse; /* section 2: demo on the left */
  }

  .mkt-text {
    flex: 0 1 400px; /* match the demo width; shrinks on narrower desktops */
    padding: 0;
  }

  .mkt-graphic {
    flex: 0 0 400px; /* fixed-width demo column */
    padding: 0;
    margin-top: 0; /* drop the vertical-stack spacer */
  }

  .mkt-graphic-inner {
    width: 100%;
    max-width: 400px; /* keep demos near their native (~376px) phone size */
    aspect-ratio: var(--mkt-desktop-aspect-ratio, var(--mkt-aspect-ratio));
  }
}

/* ============================== Footer ================================== */
.footer {
  padding-top: 16px; /* outer .padding(.top, 16) */
}

.footer-panel {
  background: var(--app-background);
  padding: 36px 32px 48px; /* 32 + top/bottom 16 */
  display: flex;
  flex-direction: column;
  gap: 48px; /* VStack(spacing: 48) */
}

.footer-row {
  display: flex;
  flex-direction: row;
  align-items: flex-start; /* HStack(alignment: .top) */
  width: 100%;
  max-width: var(--content-max); /* centered content; panel background stays full-bleed */
  margin-inline: auto;
}

.footer-spacer {
  flex: 1 1 0;
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: 8px; /* VStack(spacing: 8) */
}

.footer-muted {
  color: rgb(var(--label) / 0.6);
}

.footer-link {
  font-size: 17px;
  color: inherit;
  text-decoration: none;
}

.footer-signup {
  display: inline-flex;
  align-items: center;
  gap: 10px; /* HStack(spacing: 10) */
  font-size: 17px;
  font-weight: 500; /* .medium */
  color: var(--theme-color);
  text-decoration: none;
}

.footer-signup svg {
  position: relative;
  top: 1px; /* .offset(y: 2), nudged up 1px to align with the text */
}

/* ===================== Demo 1: infinite app grid ======================== */
.app-grid {
  position: absolute;
  inset: 0;
  overflow: hidden;
  --app-grid-scale: 1;
  --app-grid-column-gap: 24px;
  --app-grid-row-gap: 18px;
}

.grid-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
}

@media (min-width: 900px) {
  .app-grid {
    --app-grid-scale: 1.05;
    --app-grid-column-gap: 36px;
    --app-grid-row-gap: 24px;
  }
}

/* ===================== Thinking indicator =============================== */
.ti-row {
  display: flex;
  align-items: flex-start;
}

.ti-stack {
  position: relative;
  display: inline-block;
  /* outer .padding(.leading 4)/.trailing 2)/.top 8)/.bottom 18) */
  padding: 8px 2px 18px 4px;
}

.ti-capsule {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px; /* dots HStack(spacing: 4) */
  padding: 15px;
  background: #e6e6e7; /* 0xF3F3F4, brightness -0.05 */
  border-radius: 999px;
  transform-origin: bottom left; /* scaleEffect(anchor: .bottomLeading) */
  z-index: 1;
}

.ti-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(60, 60, 67, 0.6); /* .secondary */
  transform-origin: center;
}

.ti-circle {
  position: absolute;
  border-radius: 50%;
  background: #e6e6e7;
  transform-origin: center;
}

/* capsule bottomLeading is at (4, 45) within the stack */
.ti-large {
  width: 13px;
  height: 13px;
  left: 7px; /* 4 + offset x 3 */
  top: 35px; /* (45 + offset y 3) - 13 */
}

.ti-small {
  width: 6px;
  height: 6px;
  left: 1px; /* 4 + offset x -3 */
  top: 47px; /* (45 + offset y 8) - 6 */
}

/* ===================== Conversations (shared) =========================== */
.conv {
  position: absolute;
  inset: 0;
}

/* SecondPage wraps the whole animation in
   .padding(.leading, 8).padding(.trailing, 36).padding(.vertical, 8); combined
   with the inner stack's .padding(.horizontal, 18) this gives the bubbles
   leading 26 / trailing 54 — identical to page 3 — so they anchor to the left. */
.conv2 {
  padding: 8px 36px 8px 8px;
}

.conv-inner {
  position: relative;
  width: 100%;
  height: 100%;
}

.conv-stack {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* bottom-anchored VStack */
  align-items: flex-start;
  gap: 8px; /* VStack(spacing: 8) */
  padding: 0 18px 13px 18px; /* stepHorizontalPadding + paddedStepBottomSpacing 13 (message bubbles sit 21px above the container) */
}

.conv-thinking {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 18px; /* horizontal 18, no bottom: the indicator's box hugs the container floor (8px). Its ti-stack
                      has 18px internal bottom padding, so the visible dots land ~26px up — just above the bubbles. */
  display: flex;
}

/* message bubbles: SVG background behind content */
.msg-bubble {
  position: relative;
  display: inline-block;
}

.bubble-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  z-index: 0;
  overflow: visible;
}

.msg-bubble > *:not(.bubble-bg) {
  position: relative;
  z-index: 1;
}

.bubble-text {
  font-size: 17px;
  line-height: 22px; /* 17pt + lineSpacing(2) */
}

.assistant-text {
  color: #1c1c1e;
  padding: 12px 16px 19.82px 16px; /* bottom 12 + cornerRadius(23)*0.34 */
}

.user-text {
  color: #ffffff;
  padding: 12px 16px 19.82px 16px;
}

/* ---- quote bubble ---- */
.quote-bubble {
  color: #1c1c1e;
  padding: 13px 16px 20px 13px; /* top13 trailing16 bottom20 leading13 */
}

.quote-content {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 14px; /* HStack(spacing: 14) */
}

.quote-avatar {
  position: relative;
  width: 58px;
  height: 58px;
  flex: 0 0 auto;
}

.account-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  font-family: var(--font-rounded);
  font-weight: 700;
  line-height: 1;
}

.quote-gmail {
  position: absolute;
  right: -5px; /* .offset(x: 5, y: 5) from bottomTrailing */
  bottom: -5px;
  width: 24px;
  height: 24px;
  border-radius: 5px;
  object-fit: cover;
  border: 0.5px solid rgba(28, 28, 30, 0.22);
}

.quote-textcol {
  display: flex;
  flex-direction: column;
  gap: 4px; /* VStack(spacing: 4) */
  flex: 1 1 auto;
  min-width: 0;
}

.quote-titlerow {
  display: flex;
  flex-direction: row;
  align-items: baseline; /* firstTextBaseline */
  gap: 10px;
}

.quote-title {
  font-size: 17px; /* .headline */
  font-weight: 600;
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.quote-now {
  font-size: 15px; /* .subheadline */
  color: rgba(28, 28, 30, 0.58);
  white-space: nowrap;
  flex: 0 0 auto;
}

.quote-snippet {
  font-size: 16px; /* .callout */
  color: #1c1c1e;
  line-height: 20px; /* lineSpacing(-1) */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- choice bubble ---- */
.choice {
  display: flex;
  flex-direction: column;
  gap: 5px; /* VStack(spacing: 5) */
  align-items: flex-start;
  width: 100%;
}

.choice-option {
  display: flex;
  flex-direction: row;
  align-items: center;
  padding: 12px 16px 12px 20px; /* leading20 vertical12 trailing16 */
  background: color-mix(in srgb, var(--theme-color) 16%, transparent);
  border-radius: 999px;
}

.choice-option-label {
  flex: 1 1 auto;
  font-size: 16px; /* .callout */
  font-weight: 500;
  color: var(--theme-color);
  text-align: left;
}

.choice-option svg {
  color: var(--theme-color);
  margin-left: 12px;
  flex: 0 0 auto;
}

.choice-prompt {
  font-size: 12px; /* .caption */
  font-weight: 500;
  color: var(--theme-color);
  padding: 3px 3px 3px 21px; /* padding(3) + leading 18 */
}

/* ===================== Demo 3: third page conversation ================== */
.conv3 {
  padding: 8px 0; /* ThirdPage .padding(.vertical, 8) */
}

.conv-stack-3 {
  padding: 0 0 13px 0; /* no horizontal padding; items carry their own. paddedStepBottomSpacing 13 -> message bubbles sit 21px above the container */
  align-items: stretch;
}

.conv-row {
  width: 100%;
  display: flex;
}

.user-row {
  padding: 0 26px 0 54px; /* leading 36+18, trailing 8+18 (mirrored) */
  justify-content: flex-end;
}

.assistant-row {
  padding: 0 54px 0 26px; /* leading 8+18, trailing 36+18 */
  justify-content: flex-start;
}

.breadcrumb-row {
  padding: 16px 54px 6px 26px; /* top 16, bottom 6, leading 26, trailing 54 */
  justify-content: center;
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 4px; /* HStack(spacing: 4) */
  color: rgba(60, 60, 67, 0.6); /* .secondary */
}

.breadcrumb-text {
  font-size: 13px; /* .footnote */
  font-weight: 600;
}

.conv-reserve {
  width: 100%;
}

/* status indicator */
.status-indicator {
  padding: 6px 36px 0 8px; /* top 6; leading 8, trailing 36 */
  margin-bottom: -13px; /* extend through .conv-stack-3's 13px bottom padding so the working-state group
                           (status card + thinking) hugs the container floor like demo 2's thinking layer,
                           while the message rows above keep their 21px baseline. */
}

.status-col {
  display: flex;
  flex-direction: column;
  gap: 3px; /* VStack(spacing: 3) */
  align-items: flex-start;
}

.thinking-area {
  position: relative;
  padding: 0 18px; /* .padding(.horizontal, 18) */
}

.thinking-area.under-status {
  margin-top: -8px; /* .padding(.top, -8) when status visible */
}

.thinking-visible {
  position: absolute;
  left: 18px;
  bottom: 0; /* aligns with thinking-area bottom; stack padding sets the gap to the container */
}

.status-card-wrap {
  padding: 0 18px 0 21px; /* .padding(.horizontal, 18) + .padding(.leading, 3) */
}

.status-card {
  background: #e6e6e7; /* WebsiteStatusSurfaceBackground (light) */
  overflow: hidden;
}

.status-preview {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.03);
}

.status-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.status-label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px 12px 12px; /* vertical 12, leading 12, trailing 16 */
}

.status-icon {
  width: 32px;
  height: 32px;
  border-radius: 5px;
  object-fit: cover;
  flex: 0 0 auto;
}

.status-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.status-name {
  font-size: 13px; /* .footnote.medium */
  font-weight: 500;
  color: rgba(60, 60, 67, 0.6);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.status-sub {
  font-size: 12px; /* .caption */
  color: rgba(60, 60, 67, 0.408); /* .secondary.opacity(0.68) */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* status card with a static frame (browser bubble, demo 4) */
.status-frame {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 15%; /* bias toward the top so the page header shows */
  display: block;
}

/* ===================== Demo 4: inline component grid ==================== */
.bubble-grid {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.bgrid-hfade {
  position: absolute;
  inset: 0;
}

/* Fixed wrapper for the blurred duplicate track: holds the rim-band feather
   mask (set in JS) so the soft edge stays put while the track scrolls under it.
   The blur itself lives on .bgrid-track. Starts fully masked out so the
   duplicate stays hidden until layout() installs the feather mask (otherwise it
   flashes as a sharp double-image on first paint, before ResizeObserver fires). */
.bgrid-blur {
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(#0000, #0000);
  mask-image: linear-gradient(#0000, #0000);
}

/* soften any top/bottom clipping of tall columns */
.bgrid-vfade {
  position: absolute;
  inset: 0;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
}

.bgrid-track {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  display: flex;
  flex-direction: row;
  align-items: center; /* vertically center each column */
  gap: 18px; /* COL_GAP — matches the row gap in .bgrid-col */
  --bgrid-scale: 0.82; /* shrink factor (mobile); overridden larger on desktop below */
  transform-origin: left center; /* scale the whole grid from the left edge */
  transform: scale(var(--bgrid-scale, 0.82)) translateX(0);
  /* Compositor-only CSS animation: the drift stays smooth even while the page is
     being touched/scrolled (a JS rAF loop janks against scroll). */
  animation: bgrid-scroll var(--bgrid-dur, 40s) linear infinite;
  will-change: transform;
}
@keyframes bgrid-scroll {
  from {
    transform: scale(var(--bgrid-scale, 0.82)) translateX(0);
  }
  to {
    transform: scale(var(--bgrid-scale, 0.82)) translateX(calc(-1 * var(--bgrid-seq, 1340px)));
  }
}
@media (min-width: 900px) {
  .bgrid-track {
    --bgrid-scale: 1.05; /* desktop: the card is bigger, so scale the demo up to fill it */
  }
}

.bgrid-col {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 18px;
  flex: 0 0 auto; /* shrink-wrap to the column's bubble width (narrow vs wide) */
}

/* ---- quote bubble: TRUE source styling (dark #1C1C1E, white text) ---- */
.ib-quote,
.ib-quote .quote-title,
.ib-quote .quote-snippet {
  color: #ffffff;
}
/* Scaled down so the quote sits at the calendar/other bubbles' visual weight in
   the grid (the source avatar/fonts read too large here). */
.ib-quote.quote-bubble {
  padding: 11px 14px 18px 11px;
}
.ib-quote .quote-content {
  gap: 11px;
}
.ib-quote .quote-avatar {
  width: 42px;
  height: 42px;
}
.ib-quote .quote-gmail {
  width: 18px;
  height: 18px;
  right: -4px;
  bottom: -4px;
  border-color: rgba(255, 255, 255, 0.22);
}
.ib-quote .quote-titlerow {
  gap: 8px;
}
.ib-quote .quote-textcol {
  gap: 3px;
}
.ib-quote .quote-title {
  font-size: 15px;
}
.ib-quote .quote-snippet {
  font-size: 13.5px;
  line-height: 17px;
}
.ib-quote .quote-now {
  font-size: 12.5px;
  color: rgba(255, 255, 255, 0.42); /* .white.opacity(0.42) */
}

/* ---- calendar bubble ---- */
.ib-cal {
  color: #1c1c1e;
  padding: 13px 16px 19px 16px; /* top13 h16 bottom19 (incl. tail depth) */
}
.ib-cal-content {
  display: flex;
  flex-direction: column;
  gap: 6px; /* VStack(spacing: 6) */
}
.ib-cal-date {
  font-size: 11px; /* .caption.semibold */
  font-weight: 600;
  letter-spacing: 0.3px;
  color: #e2b400; /* accent (0xF7D701) darkened ~.brightness(-0.05) */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ib-cal-row {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 9px; /* HStack(spacing: 9) */
}
.ib-cal-capsule {
  flex: 0 0 3px;
  width: 3px;
  border-radius: 999px;
  background: #f7d701; /* accentColor */
}
.ib-cal-textcol {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ib-cal-title {
  font-size: 15px; /* .subheadline.semibold */
  font-weight: 600;
  color: rgb(0 0 0 / 0.9);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ib-cal-sub {
  font-size: 15px; /* .subheadline */
  color: rgba(60, 60, 67, 0.6); /* .secondary */
  letter-spacing: 0.5px; /* tracking(0.5) */
  white-space: nowrap;
}
.ib-cal-suffix {
  font-size: 12px; /* .caption.medium */
  font-weight: 500;
}

/* ---- map bubble (stylized stand-in for MapKit) ---- */
.ib-map {
  position: relative;
  background: #eceae3;
  overflow: hidden;
}
.ib-map-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 42% 170%; /* push the crop lower and slightly left; the footer masks the overflow */
  display: block;
}
.ib-map-footer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  background: #e1e1df; /* Color(hex: 0xE1E1DF) */
  padding: 8px 16px 18px 16px; /* top8 h16 bottom18 (tail) */
}
.ib-map-title {
  font-size: 15px; /* .subheadline.semibold */
  font-weight: 600;
  color: rgb(0 0 0 / 0.88);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ib-map-city {
  font-size: 13px; /* .footnote */
  color: rgba(60, 60, 67, 0.6); /* .secondary */
  margin-top: 2px; /* VStack(spacing: 2) */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- image stack (ImageDeckView >2 images) ---- */
.ib-stack {
  position: relative;
  overflow: visible;
}
.ib-stack-card {
  position: absolute;
  left: 0;
  transform-origin: center center; /* StackedItemsLayout rotates/scales about center */
  border-radius: 18px;
  overflow: hidden;
  background: #ffffff;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.18);
}
.ib-stack-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
