/* ==========================================================================
   VIBE CODING — Get ER Done + FlashCart
   Case-study stylesheet, consolidated the same way css/switching.css and
   css/ai-enterprise.css are: one file per case study, loaded alongside
   css/footer.css and nothing else.

   Assembled from, in order:
     1. TOKENS      — this page's design tokens (Figma node 728:9834)
     2. MAIN        — reset, a11y helpers, layout primitives
     3. CHROME      — site header + case-study sub-nav, lifted from
                      css/ai-enterprise.css so all five case studies share
                      one header rather than each re-deriving it
     4. COMPONENTS  — meta rail, project lockup, ruled note, CTA, media
     5. CASE STUDY  — the page's own sections

   Design source : Figma "PORTFOLIO_CASE-STUDY-LAYOUT"
                   frame 728:9834 "VIBE CODING"
                   1920 x 4057, reference width 1920px
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   Read from get_variable_defs + the frame's design context. Figma published
   only a thin variable set for this frame:

       Neutral / White  #FFFFFF · color/grey/15  #282626 · stroke weight/1

   Everything else below was read off the frame's design context and is
   annotated with the node it came from. Nothing further down this file
   should hardcode a hex value or a raw type size — reach for a token.
   ========================================================================== */

:root {

  /* ----------------------------------------------------------------------
     BREAKPOINTS
     The site-wide ladder, declared here for documentation and for JS to
     read. Media queries cannot interpolate custom properties, so the same
     numbers are repeated literally in @media rules — change one, change
     both. Identical to css/tokens.css so every page steps together.

       (base)            0     -  480px   mobile
       --bp-mobile-lg    481px -  767px   large mobile
       --bp-tablet       768px - 1023px   tablet
       --bp-desktop     1024px - 1439px   desktop
       --bp-desktop-lg  1440px +          large desktop -> approaches the
                                          1920px Figma frame exactly
     ---------------------------------------------------------------------- */
  --bp-mobile-lg:   481px;
  --bp-tablet:      768px;
  --bp-desktop:    1024px;
  --bp-desktop-lg: 1440px;

  /* Nav-only breakpoint. The header's seven nav items need more width than
     1024 can offer, so the collapsed menu covers everything under 1200 —
     the same value the other case studies use. The two-column rail layout
     still begins at --bp-desktop. */
  --bp-nav:        1200px;

  /* The viewport at which the fluid values below reach their Figma value. */
  --design-width:  1920px;


  /* ----------------------------------------------------------------------
     COLOR
     ---------------------------------------------------------------------- */

  /* Surfaces */
  --color-bg:              #fafafa;  /* page ground (728:9834)             */
  --color-band-hero:       #eee8dc;  /* hero band (729:10311)              */
  --color-band-project:    #ececec;  /* Get ER Done band (728:9866)        */
  --color-header:          #323237;  /* top header bar                     */
  --color-white:           #ffffff;

  /* Text */
  --color-ink:             #000000;  /* body copy (729:10094)              */
  --color-ink-hero:        #3c3c43;  /* hero headline (729:10338)          */
  --color-ink-eyebrow:     #333333;  /* eyebrow + hero sub (729:10336)     */
  --color-ink-wordmark:    #1c1a17;  /* "Get ER Done" (729:10046)          */
  --color-ink-lead:        #494746;  /* emphasis in the 40px lead          */
  --color-muted:           #8a857f;  /* section label, meta values, lead   */
  --color-meta-label:      #242427;  /* meta rail labels (728:9913)        */
  --color-on-dark:         #ededea;

  /* Line work — Figma variable color/grey/15 */
  --color-rule:            #282626;

  /* Accents */
  --color-link:            #0072ce;  /* LinkedIn link (729:10094)          */
  --color-cta:             #ce6234;  /* "Open App" button (734:10387)      */
  --color-flash-orange:    #ff4d17;  /* "FLASH" (734:10445)                */
  --color-flash-dark:      #323237;  /* "CART" + the app icon plate        */
  --color-focus:           #1e9aff;  /* focus ring on the video scrubber   */
  /* Hairline around the FlashCart media. The frame draws the capture at
     #b5b5b5 and the recording at #b2b2b2 — a difference well under what
     the eye resolves, so both use one value. */
  --color-media-edge:      #b2b2b2;


  /* ----------------------------------------------------------------------
     TYPOGRAPHY
     Inter throughout, except the "Get ER Done" wordmark (Permanent Marker)
     and the app's own CTA label (Source Sans 3, as the frame sets it).
     ---------------------------------------------------------------------- */

  --font-primary: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
                  Roboto, Helvetica, Arial, sans-serif;
  --font-marker:  "Permanent Marker", "Bradley Hand", "Brush Script MT", cursive;
  --font-app:     "Source Sans 3", "Source Sans Pro", var(--font-primary);

  --fw-regular:   400;
  --fw-medium:    500;
  --fw-semibold:  600;
  --fw-bold:      700;
  --fw-extrabold: 800;

  /* Fluid sizes interpolate between a 375px phone and the 1920px frame, so
     the design lands exactly at its reference width and degrades smoothly
     instead of snapping at each breakpoint. */

  /* 46 -> hero headline (729:10338) */
  --fs-hero:        clamp(1.75rem, 1.47694rem + 1.16505vw, 2.875rem);
  /* 40 -> the big lead paragraphs (729:10241, 734:10461) */
  --fs-lead:        clamp(1.5rem, 1.25728rem + 1.0356vw, 2.5rem);
  /* 60 -> project wordmarks (729:10046, 734:10445) */
  --fs-wordmark:    clamp(2.125rem, 1.73058rem + 1.68285vw, 3.75rem);
  /* 24 -> section label in the rail (729:10092) */
  --fs-section-label: clamp(1.125rem, 1.03398rem + 0.38835vw, 1.5rem);
  /* 20 -> body copy and the hero sub (729:10339, 729:10094) */
  --fs-body:        clamp(1rem, 0.93932rem + 0.2589vw, 1.25rem);

  /* Fixed sizes — small enough that scaling them would only hurt. */
  --fs-eyebrow:     0.8125rem;  /* 13 (729:10336)  */
  --fs-meta-label:  0.875rem;   /* 14 (741:15426)  */
  --fs-meta:        0.9375rem;  /* 15 (741:15427)  */
  --fs-button:      1rem;       /* 16 (734:10390)  */
  --fs-nav:         0.875rem;   /* 14 — sub-nav    */
  --fs-video-time:  0.875rem;   /* 14 — video clock */

  /* Line heights, as ratios of their own size */
  --lh-hero:        1.08;    /* 49.68 / 46 */
  --lh-lead:        1.4;     /* 56 / 40    */
  --lh-body:        1.5;     /* 30 / 20    */
  --lh-section-label: 1.5;   /* 36 / 24    */
  --lh-meta:        1.4;     /* 21 / 15    */
  --lh-meta-label:  1.1429;  /* 16 / 14 — the frame's label box. Safe to pin
                                because the labels are uppercase, so there
                                are no descenders to clip. */
  --lh-button:      1.375;   /* 22 / 16    */
  --lh-normal:      normal;

  /* Letter spacing, as em so it tracks the fluid sizes */
  --ls-eyebrow:      0.14em;     /*  1.82 at 13 */
  --ls-hero:        -0.02em;     /* -0.92 at 46 */
  --ls-lead:        -0.025em;    /* -1    at 40 */
  --ls-wordmark:    -0.005em;    /* -0.3  at 60 */
  --ls-section-label: -0.0183em; /* -0.44 at 24 */
  --ls-meta-label:      0.01em;   /*  0.14 at 14 */
  --ls-meta-label-role: -0.0314em;/* -0.44 at 14 — Role only */
  --ls-meta:            0.01em;   /*  0.15 at 15 */


  /* ----------------------------------------------------------------------
     LAYOUT
     The frame is one 1920 artboard with an 80px gutter, a 321px label rail,
     and the main content column starting at x=401.

         |<- 80 ->|<---- rail 321 ---->|<--- main column --->|<- 80 ->|
         0        80                   401                   1840  1920
     ---------------------------------------------------------------------- */

  --layout-max:  1920px;

  /* The rail is 321 at the frame width, but holding it there below 1440
     leaves too little for the content beside it — at 1024 it would eat a
     third of the viewport. It scales with the page and floors at 180, which
     is still wide enough for the longest rail label ("Appliance
     Organization" lives in the sub-nav, not here). 16.72vw hits 321 at
     exactly 1920. */
  --rail-width:  clamp(180px, 16.72vw, 321px);

  /* 20 on a phone -> 80 at the frame width */
  --gutter:      clamp(1.25rem, 0.33981rem + 3.8835vw, 5rem);

  /* The frame's rhythm is a flat 110 between every band. */
  --gap-band:    clamp(3.5rem, 2.68083rem + 3.49515vw, 6.875rem);

  /* Fixed chrome */
  --header-height: 5.625rem;   /* 90 */
  --subnav-height: 3.1875rem;  /* 51 */
  --touch-target:  44px;

  /* Spacing steps used inside sections */
  --space-8:   0.5rem;
  --space-16:  1rem;
  --space-24:  1.5rem;
  --space-32:  2rem;
  --space-48:  3rem;
  --space-80:  clamp(2rem, 1.27184rem + 3.10680vw, 5rem);   /* 32 -> 80 */
  --space-121: clamp(2rem, 0.64988rem + 5.76052vw, 7.5625rem); /* 32 -> 121 */

  /* Line work */
  --rule-width: 3px;   /* Figma stroke weight/1 renders ~2.85 at this scale */
  --rule-gap:   1rem;
}


/* ==========================================================================
   2. MAIN
   Reset, element defaults, and the layout primitives.
   ========================================================================== */

/* --------------------------------------------------------------------------
   2.1 RESET
   -------------------------------------------------------------------------- */

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

html {
  /* Anchors land below the whole sticky chrome rather than under it. The
     chrome is a flat 141px (90 header + 51 sub-nav) at every width. */
  scroll-padding-top: calc(var(--header-height) + var(--subnav-height));
  -webkit-text-size-adjust: 100%;
  /* Backstop against a stray wide child producing a sideways scrollbar.
     `clip` rather than `hidden`, and on <html> rather than <body>: `hidden`
     turns the element into a scroll container, which would make the fixed
     header stick to it instead of to the viewport. */
  overflow-x: clip;
}

body {
  margin: 0;
  background-color: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-primary);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  /* The header is fixed and carries both bars, so replace the space it no
     longer occupies in the flow. */
  padding-top: calc(var(--header-height) + var(--subnav-height));
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4,
p, figure, blockquote, dl, dd {
  margin: 0;
}

dd { margin-inline-start: 0; }

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img, picture, svg, video {
  display: block;
  max-width: 100%;
}

/* Fluid images: never let an intrinsic width push the page sideways. */
img { height: auto; }

a {
  color: inherit;
  text-decoration: none;
}

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


/* --------------------------------------------------------------------------
   2.2 ACCESSIBILITY HELPERS
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link — first thing in the tab order, revealed on focus. */
.skip-link {
  position: absolute;
  top: var(--space-8);
  left: var(--space-8);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-8) var(--space-16);
  background: var(--color-header);
  color: var(--color-on-dark);
  font-size: var(--fs-nav);
  font-weight: var(--fw-semibold);
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* One visible focus ring for the whole page. */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* --------------------------------------------------------------------------
   2.3 LAYOUT PRIMITIVES

   `.shell`      — the centred 1760 measure with the page gutter either side.
   `.rail-layout`— the two-column grid the case study hangs off. Below the
                   desktop breakpoint the rail collapses and both columns
                   stack, so the label reads as an ordinary section heading.
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.rail-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-32);
  width: 100%;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

@media (min-width: 1024px) {
  .rail-layout {
    grid-template-columns: var(--rail-width) minmax(0, 1fr);
    gap: 0;
  }
}

.rail-layout__rail    { grid-column: 1; min-width: 0; }
.rail-layout__content { grid-column: 1; min-width: 0; }

@media (min-width: 1024px) {
  .rail-layout__content { grid-column: 2; }
}

/* The rhythm between bands. */
.band + .band { margin-block-start: var(--gap-band); }

/* The frame carries the same 110 between the last band and the footer, which
   sits outside <main> and so is not caught by the rule above. */
main { padding-block-end: var(--gap-band); }


/* ==========================================================================
   3. CHROME (shared with the other case studies — see css/ai-enterprise.css)

   Values are copied verbatim from the shared system so this header renders
   pixel-identically to Boarding App, WayFinder News, Switching Carriers and
   AI / Enterprise. Keep in step if the shared system changes.
   ========================================================================== */

/* --------------------------------------------------------------------------
   3.1 SITE HEADER
   Dark bar (#323237, 90px tall in the frame) carrying the wordmark, the
   primary nav, and the LinkedIn link.
   -------------------------------------------------------------------------- */

.site-header {
  /* Fixed, carrying BOTH bars (dark header + case-study strip) as one unit.
     body carries a matching padding-top to replace the space it no longer
     occupies in the flow. */
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 20;
  background-color: var(--color-header);
}

.site-header .shell,
.case-nav .shell {
  max-width: calc(1760px + var(--gutter) * 2);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-16);
  min-height: var(--header-height);
}

.site-header__wordmark {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  color: var(--color-white);
  /* Both frames set this at a flat 32/36 — the desktop header (728:9841) and
     the mobile one (742:15539) — so it reaches 32 by the nav breakpoint and
     holds there rather than continuing to grow to 1920. It only eases down
     below that, where 32 plus three icons would not fit a phone. */
  font-size: clamp(1.375rem, 1.09056rem + 1.21359vw, 2rem);  /* 22 -> 32 at 1199 */
  font-weight: var(--fw-medium);
  line-height: 2.25rem;               /* 36 */
  letter-spacing: -0.44px;
  white-space: nowrap;
}

/* Hamburger toggle — below the nav breakpoint only. Drawn to match the
   frame's menu icon (742:15548): three 22.5 rules, 2.5 thick with round
   caps, 7.5 apart. Kept as CSS bars rather than the exported SVG so they can
   still cross into an X when the panel opens. */
.nav-toggle__box {
  display: block;
  position: relative;
  width: 22.5px;
  height: 17.5px;
}

.nav-toggle__bar,
.nav-toggle__bar::before,
.nav-toggle__bar::after {
  position: absolute;
  left: 0;
  width: 22.5px;
  height: 2.5px;
  border-radius: 1.25px;      /* the frame's round line caps */
  background-color: currentColor;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.nav-toggle__bar          { top: 7.5px; }
.nav-toggle__bar::before,
.nav-toggle__bar::after   { content: ""; }
.nav-toggle__bar::before  { top: -7.5px; }
.nav-toggle__bar::after   { top: 7.5px; }

/* Open state — the bars cross into an X. js/vibe-coding.js flips
   aria-expanded here and `is-open` on .primary-nav. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bar {
  background-color: transparent;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar::before {
  transform: translateY(7.5px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar::after {
  transform: translateY(-7.5px) rotate(-45deg);
}

/* Primary nav — a collapsible panel below the header bar on small screens. */
.primary-nav {
  display: none;
  position: absolute;
  inset-inline: 0;
  /* The header is the containing block and carries both bars, so 100% drops
     the panel clear of the sub-nav. */
  top: 100%;
  padding: var(--space-8) var(--gutter) var(--space-32);
  background-color: var(--color-header);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.24);
}

.primary-nav.is-open { display: block; }

.primary-nav__list {
  display: flex;
  flex-direction: column;
  list-style: none;
}

.primary-nav__link {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  font-size: var(--fs-nav);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  color: var(--color-on-dark);
  text-transform: uppercase;
}

.primary-nav__link:hover { color: var(--color-white); }

/* --- Header actions: mail · LinkedIn · menu ------------------------------
   Figma 742:15540. The frame sets 40 between the icon boxes and puts the
   group's right edge on the page margin. Each control is a 44 touch target
   with its glyph centred, so the gap between BOXES is the frame's 40 less
   the padding those targets add either side — which lands the glyphs where
   the frame draws them without shrinking the targets.
   ------------------------------------------------------------------------ */

.site-header__actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: clamp(0.0625rem, -0.57752rem + 2.73058vw, 1.46875rem);
  margin-inline-start: auto;
  /* The frame puts the LAST glyph's right edge on the page margin, but a 44
     target centred on a 22.5 menu icon holds it ~11 short of that. Pulling
     the group out by that padding lands the glyph on the margin without
     shrinking the target or disturbing the gaps between the icons. */
  margin-inline-end: -10.75px;
}

.site-header__mail,
.site-header__social,
.nav-toggle {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  color: var(--color-on-dark);
}

.site-header__mail-icon {
  width: 25px;
  height: 20px;
}

.site-header__mail:hover { color: var(--color-white); }

.site-header__social img {
  width: 2rem;
  height: 2rem;
  max-width: none;
}

/* --- Below the nav breakpoint: the mobile header (Figma 742:15538) -------
   A taller bar — 114 against the desktop header's 90 — carrying the wordmark
   and the three icons and nothing else. The height is a token override, so
   body's padding-top and the scroll-padding for anchors both follow it.
   ------------------------------------------------------------------------ */
@media (max-width: 1199px) {
  :root {
    --header-height: 7.125rem;        /* 114 (742:15538) */
  }

}

/* From the shared nav breakpoint the row sits inline and right-aligned, and
   the menu toggle goes - there is no collapsed panel to open. */
@media (min-width: 1200px) {
  /* Only the menu goes. The mail icon has replaced the row's "Get in touch"
     item (frame 715:8183), so it is shown at every width now. */
  .nav-toggle { display: none; }

  /* Frame 715:8183 across the 1760 content box: the wordmark sits left and
     the cluster is hard against the right margin -
       nav row (658 at its natural width) | 41 | mail 30 | 38 | LinkedIn 32
     The icon anchors stay 44px touch targets with their glyphs centred, so
     the gaps between BOXES are the frame's gaps less the padding those
     targets add: 41 - 9.5 = 34 before the group, 40.5 - 9.5 - 6 = 25 inside
     it, and the trailing target overhangs the margin by 6. */
  .site-header__inner {
    display: flex;
    align-items: center;
    gap: 34px;
  }
  .site-header__wordmark { flex: 0 0 auto; }
  .primary-nav { flex: 0 0 auto; margin-inline-start: auto; }
  .primary-nav__list { max-width: none; margin-inline: 0; }
  .site-header__actions {
    flex: 0 0 auto;
    margin-inline-start: 0;
    gap: 25px;
    margin-inline-end: -6px;
  }

  .primary-nav {
    display: block;
    position: static;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    background: none;
    box-shadow: none;
  }

  .primary-nav__list {
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    gap: clamp(1.5rem, -1rem + 3.33333vw, 3rem);   /* 24 -> 48 */
    /* No cap: frame 715:8183 lets the six items sit at their natural 658
       and pins the row to the right margin. */
    max-width: none;
    min-width: 0;
    margin-inline: 0;
  }

  .primary-nav__link {
    min-height: 66px;
    white-space: nowrap;
    text-align: center;
  }



}


/* --------------------------------------------------------------------------
   3.2 CASE-STUDY SUB-NAV
   Light 51px strip listing the case studies, with an underline marking the
   current one. Scrolls horizontally when it cannot fit.
   -------------------------------------------------------------------------- */

.case-nav {
  /* Sits inside the fixed .site-header, so the two bars pin as one unit. */
  background-color: var(--color-bg);
}

.case-nav__inner {
  display: flex;
  align-items: center;
  min-height: var(--subnav-height);
  /* Below the nav breakpoint the labels are wider than the bar, so it
     scrolls sideways rather than wrapping or shrinking the type. */
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.case-nav__inner::-webkit-scrollbar { display: none; }

.case-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 0.5rem + 2vw, 3.25rem);
  min-width: max-content;             /* keeps the labels on one line */
  list-style: none;
}

.case-nav__link {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  color: #454545;
  font-size: var(--fs-nav);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration-thickness: 1px;
  text-decoration-color: #1d1d1b;
  text-underline-offset: 7px;         /* frame: 7px under the baseline */
}

a.case-nav__link:hover {
  color: #2f2e2e;
  text-decoration-line: underline;
}

/* The project this page is. A <span>, not a link — there is nowhere for it
   to go — carrying the underline that marks it as selected. */
.case-nav__link--current { text-decoration-line: underline; }

@media (min-width: 1200px) {
  .case-nav__list {
    margin-inline-start: auto;
    margin-inline-end: 6.25%;
  }
}


/* ==========================================================================
   4. COMPONENTS
   The pieces this case study adds on top of the shared chrome.
   ========================================================================== */

/* --------------------------------------------------------------------------
   4.1 META RAIL — ROLE / TECH STACK / STATUS
   Figma 741:15424 (Get ER Done) and 741:15436 (FlashCart). A <dl>: each label
   is a term and its value the description, which is what the content actually
   is and gives screen readers the pairing for free.

   The frame draws each group as a 130-wide box: label, an 8 gap, then the
   value; 24 between one group and the next. 16 + 8 + 21 = the 45 the frame
   gives the Role and Status groups, and 45 + 24 + 150 + 24 + 45 = its 288
   total, so the measurements agree end to end.
   -------------------------------------------------------------------------- */

/* Mobile-first: below the desktop breakpoint this is not a rail at all. The
   mobile mockup (741:15483) sets the three groups side by side across the
   content measure instead of stacking them into a tall column.

   Read as an arrangement, not as measurements: equal columns across whatever
   the measure happens to be, so the row holds its proportions at any phone or
   tablet width rather than being pinned to the widths the mockup was drawn
   at. The gutter runs on the page's own spacing scale, easing from the 16
   step on a narrow phone to the 32 step by the time the rail returns.

   auto-fit rather than a flat 3, with a floor set by the widest label
   ("TECH STACK"): three across wherever three will fit, and two on a very
   narrow phone, instead of squeezing the columns until the labels themselves
   break across lines. The floor is measured off the type, not off the
   mockup. */
.meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 5.5rem), 1fr));
  gap: clamp(1rem, 0.4rem + 2.5vw, 2rem);
  align-items: start;          /* the three groups are unequal heights */
}

/* From the desktop breakpoint it becomes the left rail again: one column,
   130 wide, with 24 between groups. */
@media (min-width: 1024px) {
  .meta {
    grid-template-columns: minmax(0, 130px);
    gap: var(--space-24);
    align-content: start;
  }
}

/* Each label/value pair is its own box, so the 8 inside a group and the 24
   between groups stay independent. A <div> around a dt/dd pair is exactly
   what the HTML spec allows inside a <dl>, so the term/description pairing
   survives for assistive tech. */
.meta__group {
  display: grid;
  gap: var(--space-8);         /* 8 between label and value */
}

.meta__label {
  color: var(--color-meta-label);
  font-size: var(--fs-meta-label);
  font-weight: var(--fw-bold);
  line-height: var(--lh-meta-label);
  letter-spacing: var(--ls-meta-label);
  text-transform: uppercase;
}

/* All three labels are 14 in the frame; only their tracking differs — Role
   at -0.44, the other two at +0.14. Kept as drawn rather than levelled. */
.meta__label--role { letter-spacing: var(--ls-meta-label-role); }

.meta__value {
  color: var(--color-muted);
  font-size: var(--fs-meta);
  font-weight: var(--fw-regular);
  line-height: var(--lh-meta);
  letter-spacing: var(--ls-meta);
}

/* Get ER Done's "Launched 2025" is Medium in the frame; FlashCart's
   "In Progress" is Regular. Drawn that way, though the two rails are
   otherwise identical — worth a look on review. */
.meta__value--medium { font-weight: var(--fw-medium); }

/* The tech stack is a list of tools, one per line. */
.meta__list li { line-height: var(--lh-meta); }


/* --------------------------------------------------------------------------
   4.2 RULED NOTE
   Figma 729:10314, 734:10465 and the WORKFLOW block: a paragraph with a
   vertical rule down its left edge. Drawn in the frame as a fixed-height
   bar; a border on the block reproduces it and tracks the text height for
   free at any width.
   -------------------------------------------------------------------------- */

.ruled {
  border-inline-start: var(--rule-width) solid var(--color-rule);
  padding-inline-start: var(--rule-gap);
}

/* The frame runs the WORKFLOW paragraph to the full width of the main column
   (1417 of 1439) but holds the two project notes to 1131, leaving slack on
   the right. Capped rather than stretched so the measure stays as drawn. */
.project__content > .ruled {
  max-width: 1131px;
}


/* --------------------------------------------------------------------------
   4.3 PROJECT LOCKUP — app icon + wordmark
   Figma 729:10099 (Get ER Done) and 734:10444 (FlashCart).
   -------------------------------------------------------------------------- */

.lockup {
  display: flex;
  align-items: center;
  gap: var(--space-24);
}

.lockup__icon {
  flex: 0 0 auto;
  /* 95 at the frame width for Get ER Done, 80 for FlashCart; both ease down
     to a size that still reads on a phone. */
  width: clamp(3.25rem, 2.36408rem + 3.78vw, 5.9375rem);
  height: auto;
  border-radius: clamp(0.5rem, 0.3rem + 0.5vw, 0.75rem);
}

/* FlashCart's plate is drawn at 80 with a 10px radius. */
.lockup--sm .lockup__icon {
  width: clamp(2.75rem, 2.03883rem + 3.03398vw, 5rem);
  border-radius: 10px;
}

.lockup__name {
  font-size: var(--fs-wordmark);
  font-weight: var(--fw-bold);
  line-height: var(--lh-normal);
  letter-spacing: var(--ls-wordmark);
  /* A long wordmark must never be the thing that widens the page. */
  min-width: 0;
  overflow-wrap: break-word;
}

/* "Get ER Done" is set in the app's own marker face. */
.lockup__name--marker {
  color: var(--color-ink-wordmark);
  font-family: var(--font-marker);
  font-weight: var(--fw-regular);
}

/* "FLASHCART" — two-tone, split at the syllable. */
.lockup__name--split       { color: var(--color-flash-dark); }
.lockup__name-accent       { color: var(--color-flash-orange); }


/* --------------------------------------------------------------------------
   4.4 LEAD PARAGRAPH
   The 40/56 statement that opens each project. The frame runs an emphasised
   clause in Semi Bold #494746 and the remainder in Regular #8a857f.
   -------------------------------------------------------------------------- */

.lead {
  color: var(--color-muted);
  font-size: var(--fs-lead);
  font-weight: var(--fw-regular);
  line-height: var(--lh-lead);
  letter-spacing: var(--ls-lead);
}

.lead strong {
  color: var(--color-ink-lead);
  font-weight: var(--fw-semibold);
}

/* FlashCart's emphasis is set in black rather than #494746. */
.lead--ink strong { color: var(--color-ink); }


/* --------------------------------------------------------------------------
   4.5 CTA BUTTON
   Figma 734:10387 — the app's own pill, in the app's own face.
   -------------------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 232px;
  min-height: var(--touch-target);   /* 40 in the frame; 44 for touch */
  padding: var(--space-8) 0.75rem;
  border-radius: 32px;
  background-color: var(--color-cta);
  color: var(--color-white);
  font-family: var(--font-app);
  font-size: var(--fs-button);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-button);
  text-align: center;
  transition: background-color 0.2s ease;
}

.button:hover { background-color: #b8552c; }

/* Full width once the column is narrow enough that 232 would look stranded. */
@media (max-width: 480px) {
  .button { width: 100%; }
}


/* --------------------------------------------------------------------------
   4.6 LINK
   -------------------------------------------------------------------------- */

.link {
  color: var(--color-link);
  font-weight: var(--fw-semibold);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.link:hover { text-decoration-thickness: 2px; }


/* --------------------------------------------------------------------------
   4.7 MEDIA
   -------------------------------------------------------------------------- */

/* --- Video ---------------------------------------------------------------
   A clip that fills its slot exactly the way an image would, with the
   controls laid over the foot of it: play/pause, a scrubbable progress bar
   and elapsed/total time.

   This is the same .video component the other case studies use (the Alaska
   boarding and WayFinder heroes, and AI / Enterprise), ported to this page's
   tokens so the markup, class names and JS behaviour are identical.

   The one deliberate difference: no mute button. This clip is a silent screen
   recording, so there is no sound to control — it stays muted, which is also
   the only state any browser will autoplay. The shared controller skips the
   speaker entirely when the markup omits it.

   The control bar is not in the Figma frame — the frame draws the clip's
   poster and its hairline border and nothing else — so the bar is composed
   from existing tokens and overlaid rather than given its own space, which
   keeps the frame's layout untouched.
   ------------------------------------------------------------------------ */

.video {
  position: relative;
  overflow: hidden;
  /* .video__el is absolutely positioned, so the wrapper has no intrinsic
     width of its own. Without this it shrink-wraps to nothing inside
     .media-row, which sets justify-items: start. */
  width: 100%;
  /* The frame's hairline (734:10460) — grey now, and half a pixel. */
  border: 0.5px solid var(--color-media-edge);
  background-color: var(--color-ink);
  /* The frame's own box is 670x449 (1.4922); the 1280x852 source is 1.5023,
     so cover trims about two pixels off each side and nothing meaningful. */
  aspect-ratio: 670 / 449;
}

.video__el {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- Control bar --------------------------------------------------------- */

.video__controls {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-8) var(--space-16);
  /* A scrim, so white controls hold up over a bright frame of the clip. */
  background-image: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.55),
    rgba(0, 0, 0, 0)
  );
}

.video__toggle {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-white);
  cursor: pointer;
}

.video__icon {
  width: 24px;
  height: 24px;
}

/* Which glyph shows is driven by the state on the wrapper, so the button's
   markup never changes and its accessible name is the only thing JS swaps. */
.video__icon--pause { display: none; }
.video[data-playing="true"] .video__icon--play  { display: none; }
.video[data-playing="true"] .video__icon--pause { display: block; }

.video__time {
  flex: 0 0 auto;
  margin: 0;
  color: var(--color-white);
  font-size: var(--fs-video-time);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* --- Scrubber ------------------------------------------------------------
   A real <input type="range">, so keyboard seeking, screen-reader semantics
   and touch dragging all come for free. --p is the played fraction, set by
   js/vibe-coding.js, and paints the filled part of the track.
   ------------------------------------------------------------------------ */

.video__scrub {
  flex: 1 1 auto;
  min-width: 0;
  height: var(--touch-target);   /* full-height grab area, hairline track */
  margin: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
          appearance: none;
}

.video__scrub:focus { outline: none; }

.video__scrub:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* WebKit / Blink */
.video__scrub::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background-image: linear-gradient(
    to right,
    var(--color-white) 0 calc(var(--p, 0) * 100%),
    rgba(255, 255, 255, 0.35) calc(var(--p, 0) * 100%) 100%
  );
}

.video__scrub::-webkit-slider-thumb {
  -webkit-appearance: none;
          appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -4px;              /* centres the 12px thumb on the 4px track */
  border: 0;
  border-radius: 50%;
  background-color: var(--color-white);
}

/* Firefox */
.video__scrub::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background-color: rgba(255, 255, 255, 0.35);
}

.video__scrub::-moz-range-progress {
  height: 4px;
  border-radius: 2px;
  background-color: var(--color-white);
}

.video__scrub::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: 0;
  border-radius: 50%;
  background-color: var(--color-white);
}


/* ==========================================================================
   5. CASE STUDY — the page's own sections
   ========================================================================== */

/* --------------------------------------------------------------------------
   5.1 HERO
   Figma 729:10311 — a full-bleed #eee8dc band, 415 tall at the frame width.
   The copy starts at the main column (x=401) on a 529 measure, with the
   tooling image beside it running out to the measure's right edge.
   -------------------------------------------------------------------------- */

.hero {
  background-color: var(--color-band-hero);
  /* The band is 415 in the frame: the 327 media block with 44 clear above and
     below. The copy is shorter, so the media is what sets the height. */
  padding-block: clamp(2rem, 1.81797rem + 0.7767vw, 2.75rem);  /* 32 -> 44 */
}

.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-32);
  width: 100%;
  max-width: var(--layout-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The frame sets the copy on a 529 measure, which is what breaks the title
   across three lines: the name, then the descriptor over two. */
.hero__copy {
  max-width: 529px;
}

.hero__media {
  width: 100%;
  height: auto;
  /* 853 x 327 in the frame. */
  aspect-ratio: 853 / 327;
  object-fit: cover;
}

/* From the desktop breakpoint the copy starts at the main column — the same
   x=401 every band below it uses — whether or not the media is beside it. */
@media (min-width: 1024px) {
  .hero__inner {
    padding-inline-start: calc(var(--gutter) + var(--rail-width));
  }
}

/* The media moves alongside from 1200, not --bp-desktop. The copy column is
   held at the frame's 529 rather than sharing the width proportionally: at
   1024 a proportional split leaves it 274, which wraps the title onto a
   fourth line instead of the three the frame draws. Holding 529 keeps that
   shape and lets the media take whatever is left — which at the frame width
   is exactly its 853, since 529 + 57 + 853 is the 1439 column. */
@media (min-width: 1200px) {
  .hero__inner {
    grid-template-columns: 529px minmax(0, 1fr);
    gap: clamp(1.5rem, 0.99938rem + 2.13592vw, 3.5625rem);   /* 24 -> 57 */
    align-items: center;
  }
}

.hero__eyebrow {
  color: var(--color-ink-eyebrow);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  line-height: var(--lh-normal);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}

.hero__title {
  margin-block-start: var(--space-8);
  color: var(--color-ink-hero);
  font-size: var(--fs-hero);
  font-weight: var(--fw-regular);
  line-height: var(--lh-hero);
  letter-spacing: var(--ls-hero);
}

/* The frame sets the project name on its own line in Extra Bold and the
   descriptor beneath it in Regular, both at the same size. Caps come from
   text-transform rather than the markup, so the accessible name stays
   "Vibe Coding". */
.hero__title-name {
  display: block;
  font-weight: var(--fw-extrabold);
  text-transform: uppercase;
}

.hero__sub {
  margin-block-start: var(--space-8);
  color: var(--color-ink-eyebrow);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* --------------------------------------------------------------------------
   5.2 WORKFLOW
   Figma 741:10545 — the WORKFLOW label in the rail, a ruled paragraph and
   the tech-stack strip in the main column.
   -------------------------------------------------------------------------- */

.section-label {
  color: var(--color-muted);
  font-size: var(--fs-section-label);
  font-weight: var(--fw-medium);
  line-height: var(--lh-section-label);
  letter-spacing: var(--ls-section-label);
  text-transform: uppercase;
}

.workflow__body {
  /* The frame sets the rule 19px clear of the text here, slightly wider than
     the 16 it uses further down the page. */
  --rule-gap: clamp(1rem, 0.9rem + 0.2vw, 1.1875rem);
}

.workflow__tools {
  margin-block-start: clamp(1.5rem, 1.18146rem + 1.35922vw, 2.8125rem); /* 24 -> 45 */
  width: min(100%, 679px);
  height: auto;
}


/* --------------------------------------------------------------------------
   5.3 PROJECT BANDS
   Figma 728:9866 (Get ER Done, a full-bleed #ececec band) and 741:10539
   (FlashCart, on the page ground). Both hang off the rail grid, so their
   content lands on the same x=401 column as the WORKFLOW block above —
   which is where the frame now puts FlashCart's column too (741:10538).
   -------------------------------------------------------------------------- */

/* Only the filled band needs internal padding — it has to hold its content
   clear of its own colour. FlashCart sits on the page ground, where the 110
   band rhythm already separates it, so it takes none. */
.project--band {
  background-color: var(--color-band-project);
  padding-block: clamp(2.5rem, 1.78698rem + 3.04207vw, 5.4375rem);  /* 40 -> 87 */
}

.project__content > * + * {
  margin-block-start: var(--space-48);
}

/* --- The Get ER Done showcase: devices beside the lead + CTA ------------- */

.showcase {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-32);
}

/* The frame sets 40 between the lockup and the devices row, tighter than the
   48 the section rhythm would otherwise give it. Declared after that rule so
   it wins on source order. */
.project__content > .showcase {
  margin-block-start: clamp(1.5rem, 1.25728rem + 1.0356vw, 2.5rem);  /* 24 -> 40 */
}

/* The frame lays this row out at 1414 (764 + 121 + 529) inside the 1439
   column rather than filling it. Capped so the two tracks resolve to exactly
   764 and 529 at the frame width. */
.showcase { max-width: 1414px; }

/* Split at 1200 rather than --bp-desktop, for the same reason as the hero:
   a 40px lead paragraph needs more than the ~280px this column would get at
   1024 once the rail has taken its share. */
@media (min-width: 1200px) {
  .showcase {
    grid-template-columns: minmax(0, 764fr) minmax(0, 529fr);
    gap: var(--space-121);
    align-items: stretch;
  }
}

.showcase__devices {
  width: 100%;
  height: auto;
}

/* The frame crops the lockup to 764x520 from the top. `align-self: start`
   keeps that crop: the aside beside it is the taller column below 1920 (the
   40px lead wraps further), and a stretched image would crop away the phone. */
@media (min-width: 1200px) {
  .showcase__devices {
    align-self: start;
    aspect-ratio: 764 / 520;
    object-fit: cover;
    object-position: top;
  }
}

.showcase__aside {
  display: flex;
  flex-direction: column;
  /* The frame drops the button to the foot of the 520 column. */
  justify-content: space-between;
  gap: var(--space-32);
}

/* --- FlashCart's media row: phone capture beside the screen recording ---- */

.media-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-32);
  /* Deliberately NOT justify-items: start — the figures would shrink-wrap to
     their captions instead of filling their tracks. */
  /* The frame lays this row out at 1237 (437 + 130 + 670) inside the 1439
     column rather than filling it, so it is capped and scales down from
     there instead of stretching the recording. */
  max-width: 1237px;
}

/* The frame gives the pair of screens 437 and the recording 670, 130 apart,
   with the two groups sitting on a common baseline rather than a common top —
   the captions line up, not the tops of the pictures. */
@media (min-width: 768px) {
  .media-row {
    grid-template-columns: minmax(0, 437fr) minmax(0, 670fr);
    gap: clamp(2rem, 0.51338rem + 6.34304vw, 8.125rem);   /* 32 -> 130 */
    align-items: end;
  }
}

/* The two screens inside the first figure: 197 and 200, 40 apart (743:15630).
   They stay side by side at every width — they are a before/after pair and
   only read as one next to the other. */
.media-pair {
  display: grid;
  grid-template-columns: minmax(0, 197fr) minmax(0, 200fr);
  gap: clamp(1rem, 0.63592rem + 1.5534vw, 2.5rem);        /* 16 -> 40 */
  align-items: center;
}

.media-pair__shot {
  width: 100%;
  height: auto;
}

/* Each media item is a figure: the picture, then its caption 8 beneath.
   (743:15575 / 743:15601 — 14px Regular, black, sitting at 458 against a
   450-tall row.) */
.media-row__item {
  display: grid;
  /* Pinned to the figure's own width. Left implicit, the column would size to
     max-content — and the caption below is allowed to run wider than the
     picture, which would drag the picture out with it. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  margin: 0;
  min-width: 0;
}

.media-caption {
  font-size: var(--fs-meta-label);      /* 14 */
  font-weight: var(--fw-regular);
  line-height: var(--lh-normal);
  color: var(--color-ink);
}

/* The pair's caption is 425 against its 437 column, so unlike the single
   capture it used to sit under, it needs no room borrowed from the gutter. */


/* --- Where the meta row sits while stacked -------------------------------
   Below the desktop breakpoint the rail is not a rail, and the mockup does
   not leave it stranded at the top of the section: it sits under the
   project's opening visual — beneath the laptop-and-phone lockup on
   Get ER Done, beneath the opening statement on FlashCart — and above the
   supporting media.

   It cannot simply be moved in the markup: on desktop it has to be the first
   child of the rail grid to occupy column 1, and the devices image it now
   follows is paired with the copy beside it inside .showcase. So the two
   wrappers collapse to `display: contents` here, which lifts their children
   into the one stacked column, and `order` sequences them. Both wrappers are
   plain divs, so nothing semantic is lost.

   Only below 1024. From there up the rail returns, the wrappers get their
   boxes back, and this whole block stops applying.
   ------------------------------------------------------------------------ */

@media (max-width: 1023px) {
  .project__content,
  .showcase {
    display: contents;
  }

  /* Both wrappers' children are now items of .rail-layout, which carries its
     own row gap — so the rhythm those wrappers used to supply has to go, or
     it would stack on top of the gap. */
  .project__content > * + *,
  .project__content > .showcase {
    margin-block-start: 0;
  }

  .lockup                  { order: 1; }
  .showcase__devices       { order: 2; }   /* Get ER Done */
  /* Child combinator on purpose: FlashCart's lead is a block of the content
     column, but Get ER Done's is nested inside .showcase__aside, which is a
     flex container — an unscoped rule would order it there too and drop it
     below the Open App button. */
  .project__content > .lead { order: 2; }  /* FlashCart   */
  .meta                    { order: 3; }
  .showcase__aside         { order: 4; }   /* Get ER Done */
  .media-row               { order: 4; }   /* FlashCart   */
  .ruled                   { order: 5; }
  .project__screens        { order: 6; }
}
