body{margin:0;background:#FAFAF8;color:#10141C;font-family:Poppins,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
*{box-sizing:border-box}
a{color:#2E6BFF;text-decoration:none}
a:hover{color:#0E9AB8}
:focus-visible{outline:2px solid #0E9AB8;outline-offset:3px;border-radius:4px}
@property --cvsn{syntax:"<integer>";inherits:false;initial-value:0}
@keyframes cvsCount{to{--cvsn:100}}
@keyframes cvsBoot{0%,62%{opacity:1;visibility:visible}100%{opacity:0;visibility:hidden}}
@keyframes cvsBootBar{0%{transform:scaleX(0)}70%{transform:scaleX(.85)}100%{transform:scaleX(1)}}
@keyframes cvsBootMark{0%{opacity:0;transform:scale(.86)}45%{opacity:1;transform:scale(1)}100%{opacity:1;transform:scale(1)}}
@keyframes cvsShimmer{0%{background-position:0% 50%}100%{background-position:200% 50%}}
@keyframes cvsTicker{from{transform:translateX(0)}to{transform:translateX(-100%)}}
@keyframes cvsStrikeIn{from{text-decoration-color:transparent}to{text-decoration-color:rgba(16,20,28,0.4)}}
@keyframes cvsStrike{0%{transform:scaleX(0)}100%{transform:scaleX(1)}}
@keyframes cvsRise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@supports (animation-timeline: view()){[data-reveal]{animation-timeline:view();animation-range:entry 5% cover 20%}}
@keyframes cvsDrift{0%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(4%,-3%,0) scale(1.08)}100%{transform:translate3d(0,0,0) scale(1)}}
@keyframes cvsDrift2{0%{transform:translate3d(0,0,0) scale(1.05)}50%{transform:translate3d(-5%,4%,0) scale(1)}100%{transform:translate3d(0,0,0) scale(1.05)}}
@keyframes cvsPulse{0%,100%{opacity:.35;transform:scale(1)}50%{opacity:1;transform:scale(1.35)}}
@keyframes cvsBar{from{transform:scaleY(.35)}to{transform:scaleY(1)}}
@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}}
@media (min-width:900px){[data-navlinks]{display:flex!important}[data-burger]{display:none!important}}
[data-ticker]:hover div{animation-play-state:paused}
summary::-webkit-details-marker{display:none}
summary::after{content:"+";float:right;color:#2E6BFF;font-weight:500;margin-left:12px}
details[open] summary::after{content:"−"}
details[open]>p{animation:cvsRise .4s ease both}
input::placeholder,textarea::placeholder{color:#6C7585}
input:focus,textarea:focus{border-color:#2E6BFF!important;outline:none}
.cvs-boot-pct::after{content:counter(cvsp) '%'}
#mobile-menu[hidden]{display:none!important}
.hv-dark:hover{color:#10141C}
.hv-navlink:hover{background:rgba(16,20,28,0.06);color:#10141C}
.hv-card:hover{color:#10141C;transform:translateY(-5px);border-color:rgba(46,107,255,0.5);box-shadow:0 20px 50px -25px rgba(46,107,255,0.5)}
.hv-workcard:hover{color:#10141C;transform:translateY(-5px);border-color:rgba(46,107,255,0.45)}
.hv-social:hover{border-color:rgba(46,107,255,0.6);color:#10141C}
.hv-link:hover{color:#2E6BFF;text-decoration:underline}
.hv-cta1:hover{transform:translateY(-2px);box-shadow:0 16px 38px rgba(242,100,48,0.46)}
.hv-cta2:hover{color:#170F06;transform:translateY(-3px);box-shadow:0 18px 46px rgba(242,100,48,0.5)}
.hv-cta3:hover{color:#170F06;transform:translateY(-3px);box-shadow:0 16px 40px rgba(242,100,48,0.48)}
.hv-cta4:hover{color:#170F06;transform:translateY(-2px);box-shadow:0 10px 28px rgba(242,100,48,0.45)}
.hv-outline:hover{color:#10141C;border-color:rgba(46,107,255,0.6);background:rgba(46,107,255,0.08)}
.hv-readstory:hover{color:#10141C;background:rgba(46,107,255,0.2)}

/* Pulse in the nav. It is the item we want clicked, but it is a live feed and
   not an offer, so it takes the blue system accent already used for eyebrows,
   focus rings and card hovers rather than a second orange pill next to the
   booking CTA. The pill is quieter than the CTA and louder than a plain link,
   which is the order of priority we actually want. The dot carries the "live"
   read; the reduced-motion rule above already stops it animating. */
.hv-navpulse{display:inline-flex;align-items:center;gap:6.5px;padding:8px 13px 8px 11px;border-radius:999px;border:1px solid rgba(46,107,255,0.22);background:rgba(46,107,255,0.07);color:#2E6BFF;font-size:14px;font-weight:500;white-space:nowrap;transition:background .2s ease,border-color .2s ease}
.hv-navpulse:hover{background:rgba(46,107,255,0.14);border-color:rgba(46,107,255,0.45);color:#2E6BFF}
.hv-navpulse[aria-current="page"]{background:rgba(46,107,255,0.16);border-color:rgba(46,107,255,0.52)}
.hv-navdot{position:relative;flex:none;width:6px;height:6px;border-radius:50%;background:#2E6BFF}
.hv-navdot::after{content:"";position:absolute;inset:-3px;border-radius:50%;border:1.5px solid rgba(46,107,255,0.5);animation:cvsPulseDot 2.4s ease-out infinite}
@keyframes cvsPulseDot{0%{transform:scale(.55);opacity:.85}70%{transform:scale(1.6);opacity:0}100%{transform:scale(1.6);opacity:0}}

/* In the mobile sheet the items are 32px, so a pill would look like a button
   dropped into a list. The label just takes the accent colour and carries the
   same dot plus a small "live" tag, keeping the 32px rhythm intact. */
.hv-menupulse{display:flex;align-items:center;gap:11px;color:#2E6BFF}
.hv-navdot-lg{width:8px;height:8px}
.hv-navdot-lg::after{inset:-4px;border-width:2px}
.hv-menupulse-note{font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#2E6BFF;background:rgba(46,107,255,0.10);border-radius:999px;padding:3px 8px}

.blog-post-body h2{font-family:Poppins,sans-serif;font-size:22px;font-weight:600;letter-spacing:-.02em;margin:36px 0 12px;color:#10141C}
.blog-post-body h3{font-family:Poppins,sans-serif;font-size:18px;font-weight:600;letter-spacing:-.02em;margin:26px 0 10px;color:#10141C}
.blog-post-body h4{font-family:Poppins,sans-serif;font-size:15.5px;font-weight:600;letter-spacing:-.01em;margin:20px 0 8px;color:#10141C}
.blog-post-body p{margin:0 0 18px;color:#2b313d}
.blog-post-body ul,.blog-post-body ol{margin:0 0 18px;padding-left:22px;color:#2b313d}
.blog-post-body li{margin-bottom:8px}
.blog-post-body a{color:#2E6BFF;text-decoration:underline}
.blog-post-body strong{color:#10141C}

@media (max-width:420px){
  #site-header nav{padding-left:16px!important;padding-right:16px!important;gap:12px!important}
  #site-header nav>a.hv-dark{gap:0!important}
  #site-header img[src$="logo.png"]{height:22px!important}
  #site-header a.hv-cta4{padding:8px 10px!important;font-size:12.5px!important;margin-left:0!important}
  #site-header button[data-burger]{width:36px!important;height:36px!important;padding:0 8px!important}
}
.blog-post-body img{max-width:100%;height:auto;border-radius:12px;display:block;margin:28px 0}

/* Pulse: the working log. A single hairline rail runs from the first marker
   down to the oldest entry, so the rail itself marks the span of time on
   screen rather than decorating the edge of the page. The working day sits
   once in the gutter; each entry under it carries only its own relative time,
   which app.js keeps current. The feed is capped well short of the 1200px
   shell because these are paragraphs to read, not cards to scan. */
.pulse-feed{list-style:none;margin:0;padding:0;max-width:900px}
.pulse-daymark,.pulse-entry{position:relative;display:grid;grid-template-columns:150px 1fr;column-gap:26px;animation:cvsRise .55s cubic-bezier(.2,.7,.2,1) both;animation-delay:calc(var(--i,0)*65ms)}
.pulse-daymark::before,.pulse-entry::before{content:"";position:absolute;left:150px;top:0;bottom:0;width:1px;background:rgba(16,20,28,0.11)}
.pulse-feed>.pulse-railhead::before{top:22px}
.pulse-feed>.pulse-railtail::before{bottom:auto;height:26px}
.pulse-daymark{padding-bottom:14px}
.pulse-daymark span{padding-top:14px;padding-right:20px;text-align:right;font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#8A93A1}
.pulse-entry{padding-bottom:34px}
.pulse-entry::after{content:"";position:absolute;left:150px;top:26px;width:9px;height:9px;margin:-4px 0 0 -4px;border-radius:50%;background:var(--pulse-accent,#6C7585);box-shadow:0 0 0 4px #FAFAF8}
.pulse-daymark[hidden],.pulse-entry[hidden]{display:none}
.pulse-entry[data-latest]::after{animation:cvsPulse 2.8s ease-in-out infinite}
/* The site's blanket reduced-motion rule uses `*`, which never matches a
   pseudo-element, so the dot has to be stopped by name. The `latest` chip
   still marks the newest entry when nothing is moving. */
@media (prefers-reduced-motion: reduce){.pulse-entry[data-latest]::after{animation:none}}
/* Both halves of the stamp sit in the gutter: the relative time on top, which
   app.js keeps current, and the fixed absolute below it. The absolute is always
   Asia/Kolkata and says so, so a reader in another timezone reads it as the
   studio's clock rather than as a stamp that's five and a half hours wrong. */
.pulse-meta{padding-top:16px;padding-right:20px}
.pulse-time{display:flex;flex-direction:column;align-items:flex-end;gap:2px;font-family:'IBM Plex Mono',monospace;font-variant-numeric:tabular-nums;text-align:right}
.pulse-rel{font-size:12px;line-height:1.45;color:#4B5361}
.pulse-rel[hidden]{display:none}
.pulse-abs{font-size:10.5px;line-height:1.45;color:#8A93A1;text-wrap:balance}
.pulse-card{border:1px solid rgba(16,20,28,0.055);border-left:2px solid var(--pulse-accent,#6C7585);border-radius:14px;background:#FFFFFF;padding:22px 24px;box-shadow:0 1px 2px rgba(16,20,28,.03),0 18px 40px -30px rgba(16,20,28,.5)}
.pulse-kindrow{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
/* The kind chip carries an icon and the kind's name as well as its hue. That's
   deliberate: six hues can't be told apart under protanopia at the separation
   the palette would need, so colour is never the only signal. */
.pulse-kind{display:inline-flex;align-items:center;gap:5px;font-family:'IBM Plex Mono',monospace;font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--pulse-accent,#6C7585);background:rgba(16,20,28,0.04);background:color-mix(in srgb,var(--pulse-accent,#6C7585) 9%,transparent);border-radius:999px;padding:3px 9px 3px 7px;line-height:1.5}
.pulse-icon{flex:none;display:block}
.pulse-latest{font-family:'IBM Plex Mono',monospace;font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:#6C7585;border:1px solid rgba(16,20,28,0.13);border-radius:999px;padding:2px 8px;line-height:1.5}
.pulse-title{margin:11px 0 12px;font-family:Poppins,sans-serif;font-size:20.5px;font-weight:600;letter-spacing:-.025em;line-height:1.28;color:#10141C;text-wrap:pretty}
.pulse-body{color:#4B5361;font-size:15px;line-height:1.75}
.pulse-body p{margin:0 0 12px}
.pulse-body p:last-child{margin-bottom:0}
.pulse-body code{font-family:'IBM Plex Mono',monospace;font-size:.9em;padding:1px 5px;border-radius:5px;background:rgba(16,20,28,0.05);color:#10141C}
.pulse-body strong{color:#10141C;font-weight:600}
.pulse-body em{color:#10141C;font-style:normal;font-weight:500}
/* Homepage strip: show the opening paragraph only, clamped. Clamping the whole
   body would run two paragraphs together mid-sentence. */
.pulse-body-clamp p{display:none}
.pulse-body-clamp p:first-child{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;margin:0}
/* The expand is only written in by app.js when the body actually overflows, so
   an entry short enough to read whole never grows a control that does nothing. */
.pulse-more{display:inline-flex;align-items:center;gap:5px;margin-top:9px;padding:0;border:0;background:none;cursor:pointer;font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.05em;color:#6C7585}
.pulse-more:hover{color:var(--pulse-accent,#2E6BFF)}
.pulse-more::after{content:"";width:5px;height:5px;border-right:1.4px solid currentColor;border-bottom:1.4px solid currentColor;transform:translateY(-2px) rotate(45deg);transition:transform .18s ease}
.pulse-more[aria-expanded="true"]::after{transform:translateY(1px) rotate(-135deg)}

/* Attribution and tags share the card's last row. On a wide card they sit on
   one line; the tags wrap under the names when there isn't room for both. */
.pulse-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 16px;margin-top:16px;padding-top:14px;border-top:1px solid rgba(16,20,28,0.07)}
.pulse-who{display:flex;align-items:center;gap:9px;min-width:0}
.pulse-faces{display:inline-flex;flex:none}
.pulse-face{width:23px;height:23px;border-radius:50%;object-fit:cover;object-position:center top;background:#F4F4F0;box-shadow:0 0 0 2px #FFFFFF}
.pulse-faces>*+*{margin-left:-8px}
/* No photo means initials on that person's own tint. A stock silhouette would
   be the one invented thing on a page whose argument is that nothing here is. */
.pulse-face-init{display:flex;align-items:center;justify-content:center;font-family:'IBM Plex Mono',monospace;font-size:9px;font-weight:500;letter-spacing:.02em;color:var(--face-tint,#6C7585);background:#F1F3F7;background:color-mix(in srgb,var(--face-tint,#6C7585) 15%,#FFFFFF)}
.pulse-who-text{font-size:12.5px;line-height:1.5;color:#6C7585;min-width:0}
.pulse-who-names{color:#4B5361}
/* Attribution by role rather than by face, which is what most entries honestly
   are. There is no photograph of "Engineering", so the role gets a filled pill
   in the page's own typeface instead of a circle with initials in it: a circle
   would read either as an avatar that failed to load or as a person we made up,
   and both are the sort of thing this page exists to not do. It is a third
   register on purpose -- fill borrowed from the kind mark, type from the project
   chip -- and it never shares a row with the kind mark it borrowed from. */
.pulse-role{display:inline-flex;align-items:center;flex:none;font-family:Poppins,sans-serif;font-size:12px;font-weight:500;letter-spacing:-.005em;line-height:1.5;color:#3A4250;color:color-mix(in srgb,var(--role,#3A4250) 72%,#10141C);background:rgba(16,20,28,0.05);background:color-mix(in srgb,var(--role,#6C7585) 11%,transparent);border-radius:999px;padding:3px 11px;white-space:nowrap}
.pulse-dur::before{content:"·";margin:0 6px;color:#B4BBC6}
/* With a role chip and no names in front of it there is nothing for the dot to
   separate, so it would sit against the pill as a stray mark. */
.pulse-dur-solo::before{content:none;margin:0}
.pulse-tags{list-style:none;display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0}
.pulse-tags li{font-family:'IBM Plex Mono',monospace;font-size:10.5px;line-height:1.5;color:#6C7585;background:rgba(16,20,28,0.045);border-radius:5px;padding:2px 7px}

/* The kind palette. Validated against the dataviz six checks on both #FAFAF8
   and the card's #FFFFFF: lightness band, chroma floor, adjacent-pair CVD
   separation (worst 9.9 deutan / 12.6 tritan), normal-vision floor (23.2) and
   3:1 contrast all pass. The declaration order below is the documented
   adjacent-pair order -- blue, orange, teal, violet, green, rose -- chosen so
   the pairs that collapse under protanopia (orange/green) and in normal vision
   (blue/violet) never sit next to each other in it. Re-order at your peril. */
.pulse-entry[data-kind="build"],.pulse-stackcard[data-kind="build"]{--pulse-accent:#2E6BFF}
.pulse-entry[data-kind="debug"],.pulse-stackcard[data-kind="debug"]{--pulse-accent:#F26430}
.pulse-entry[data-kind="experiment"],.pulse-stackcard[data-kind="experiment"]{--pulse-accent:#0E9AB8}
.pulse-entry[data-kind="learning"],.pulse-stackcard[data-kind="learning"]{--pulse-accent:#8B4FE8}
.pulse-entry[data-kind="win"],.pulse-stackcard[data-kind="win"]{--pulse-accent:#1F8F3D}
.pulse-entry[data-kind="discussion"],.pulse-stackcard[data-kind="discussion"]{--pulse-accent:#D4568F}

/* The project chip. Three things sit near each other on an entry -- the kind,
   the project and the tags -- and they have to stay three things rather than
   blurring into one band of chips, so each is a different register:

     kind     filled pill, tinted with the kind accent, icon + UPPERCASE MONO
     project  no fill, the page's own face in ink, a solid accent dot in front
     tags     flat grey squares, lowercase mono, smallest of the three

   That split is doing real work. Twelve categorical hues -- six kinds plus six
   projects -- cannot all be told apart; a validated set tops out around eight.
   So colour never has to carry the difference between the two families: form,
   type and position do, and both chips always spell their value out in text.

   The project accents themselves were validated as their own six-slot
   categorical palette on the card's #FFFFFF surface (dataviz six checks,
   adjacent pairlist): lightness band, chroma floor, CVD separation (worst
   adjacent 16.2 deutan), normal-vision floor (22.5) and 3:1 contrast all pass.
   Declaration order below is that validated adjacent order -- re-order at your
   peril. `our-site` is deliberately outside it, in ink rather than a hue: it is
   the one project that is us rather than a client. */
.pulse-proj{display:inline-flex;align-items:center;gap:7px;font-family:Poppins,sans-serif;font-size:12px;font-weight:500;letter-spacing:-.005em;line-height:1.5;color:#3A4250;white-space:nowrap}
.pulse-proj-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--proj,#6C7585)}
.pulse-kindrow .pulse-proj{margin-left:auto}
.pulse-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* The project row. Pills rather than the rail the ranges use, because the two
   filters compose and a reader has to see at a glance which control they're
   touching. Selected state is the accent, not a generic blue: the chip you
   picked and the dots on the entries below it are then the same colour. */
.pulse-projects{display:flex;flex-wrap:wrap;gap:7px;max-width:900px;margin:0 0 30px}
.pulse-projects[hidden]{display:none}
.pulse-projbtn{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;border:1px solid rgba(16,20,28,0.11);border-radius:999px;background:#FFFFFF;cursor:pointer;font-family:Poppins,sans-serif;font-size:12.5px;font-weight:500;color:#4B5361;transition:border-color .18s ease,background .18s ease,color .18s ease}
.pulse-projbtn:hover{border-color:rgba(16,20,28,0.26);color:#10141C}
.pulse-projbtn[aria-pressed="true"]{color:var(--proj,#10141C);border-color:var(--proj,#10141C);background:color-mix(in srgb,var(--proj,#10141C) 7%,#FFFFFF);font-weight:600}
.pulse-projbtn[data-project="all"][aria-pressed="true"]{color:#10141C;border-color:rgba(16,20,28,0.4);background:rgba(16,20,28,0.05)}

/* ── The homepage overview ────────────────────────────────────────────────────
   Its own section below the fold: a panel with the argument on the left and a
   stack of the four most recent entries on the right, cycling one slot at a
   time. The stack is the section's one piece of motion; everything around it
   is still. */
.pulse-overview{padding:0 24px 120px;animation:cvsRise .8s cubic-bezier(.2,.7,.2,1) both}
.pulse-overview-shell{position:relative;max-width:1200px;margin:0 auto;border:1px solid rgba(16,20,28,0.10);border-radius:24px;overflow:hidden;background:linear-gradient(150deg,#FFFFFF,#FBFBF9 60%,#F7F8FA)}
.pulse-overview-glow{position:absolute;top:-190px;right:-140px;width:560px;height:520px;border-radius:50%;background:radial-gradient(circle,rgba(46,107,255,0.16),transparent 64%);filter:blur(34px);pointer-events:none}
.pulse-overview-grid{position:relative;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(32px,4vw,60px);align-items:center;padding:clamp(28px,4vw,56px)}
.pulse-eyebrow{font-family:'IBM Plex Mono',monospace;font-size:11.5px;letter-spacing:0.1em;text-transform:uppercase;color:#2E6BFF}
.pulse-overview-copy h2{font-family:Poppins,sans-serif;font-size:clamp(26px,3vw,38px);font-weight:600;letter-spacing:-0.03em;line-height:1.1;margin:14px 0 16px;text-wrap:balance}
.pulse-overview-copy p{margin:0 0 18px;color:#4B5361;font-size:16px;line-height:1.72;max-width:480px;text-wrap:pretty}
.pulse-overview-live{margin:0 0 24px!important;font-family:'IBM Plex Mono',monospace;font-size:12px;line-height:1.9;color:#6C7585!important}
.pulse-overview-live b{color:#10141C;font-weight:500}
.pulse-overview-live time{font-variant-numeric:tabular-nums}
/* Only the relative half here. The feed shows both because it is a record you
   might cite; this line is a status, and "3 hours ago, 1 Aug 2026, 5:26 PM IST"
   reads as one mangled timestamp rather than two deliberate ones. */
.pulse-overview-live .pulse-rel:not([hidden])+.pulse-abs{display:none}
.pulse-livedot{display:inline-block;vertical-align:middle;width:7px;height:7px;margin:-2px 9px 0 0;border-radius:50%;background:#0E9AB8;animation:cvsPulse 2.8s ease-in-out infinite}
.pulse-overview-cta{display:inline-flex;align-items:center;gap:8px;padding:12px 20px;border-radius:12px;border:1px solid rgba(46,107,255,0.32);background:rgba(46,107,255,0.07);color:#2E6BFF;font-size:14.5px;font-weight:600;transition:background .2s ease,border-color .2s ease,transform .2s ease}
.pulse-overview-cta:hover{color:#2E6BFF;background:rgba(46,107,255,0.13);border-color:rgba(46,107,255,0.55);transform:translateY(-2px)}

/* The stack. Cards are absolutely positioned into four slots and swap slots on
   a tick; the one leaving goes up and out, then is put back at the bottom with
   the transition off so it doesn't fly down the card on the way. */
.pulse-stackwrap{--card-h:212px;position:relative;width:min(100%,470px);justify-self:end}
/* Depth here cannot be carried by opacity. The section sits on near-white, so a
   white card at 60% is still near-white: the cards behind read as a smudge under
   the front one rather than as paper. So the back slots get an opaque,
   progressively cooler fill and a real edge, and opacity is left alone.

   The cards scale from their top edge, so a slot's visible peek is its offset
   minus the height the scale takes off the bottom -- at 212px tall, 22px down at
   0.965 leaves about 15px showing, not 22. The offsets below are picked so each
   slot peeks the same ~15px past the one in front; change a scale and its offset
   has to follow or the rhythm goes uneven. */
.pulse-stack{list-style:none;margin:0;padding:0;position:relative;height:calc(var(--card-h) + 34px)}
.pulse-stackcard{position:absolute;left:0;right:0;top:0;transform-origin:50% 0;transition:transform .62s cubic-bezier(.22,.7,.2,1),opacity .5s ease}
.pulse-stackcard[data-pos="0"]{transform:translate3d(0,0,0) scale(1);z-index:4}
.pulse-stackcard[data-pos="1"]{transform:translate3d(0,22px,0) scale(.965);z-index:3}
.pulse-stackcard[data-pos="2"]{transform:translate3d(0,44px,0) scale(.93);z-index:2}
.pulse-stackcard[data-pos="3"]{transform:translate3d(0,60px,0) scale(.9);opacity:0;z-index:1}
/* Only the front card shows its contents. Fading the whole card instead left a
   ghosted second byline sitting a few pixels under the real one, which reads as
   a rendering fault rather than as a stack; the cards behind are blank slabs and
   the text fades in as one arrives. */
.pulse-stackcard>article>*{transition:opacity .35s ease}
.pulse-stackcard:not([data-pos="0"])>article>*{opacity:0}
/* Back slabs: flat fill, hairline edge, no shadow and no kind accent. The accent
   is a 2px left border, and on a scaled-down card it landed a few pixels inside
   the front card's edge -- a stray green or purple sliver poking out under the
   corner, which looked like a rendering fault rather than a card behind. The
   border stays 2px so the geometry doesn't shift; only its colour goes. */
.pulse-stackcard:not([data-pos="0"])>article{background:#F4F5F7;border-color:rgba(16,20,28,0.09);border-left-color:transparent;box-shadow:none}
.pulse-stackcard[data-pos="2"]>article{background:#EDEFF3}
.pulse-stackcard[data-pos="3"]>article{background:#E7EAEF}
.pulse-stackcard.is-out{transform:translate3d(0,-30px,0) scale(1.015);opacity:0;z-index:5}
.pulse-stackcard.is-snap{transition:none}
.pulse-stackcard>article{height:var(--card-h);display:flex;flex-direction:column;border:1px solid rgba(16,20,28,0.07);border-left:2px solid var(--pulse-accent,#6C7585);border-radius:16px;background:#FFFFFF;padding:19px 21px;box-shadow:0 2px 4px rgba(16,20,28,.03),0 26px 50px -34px rgba(16,20,28,.55);transition:background .45s ease,border-color .45s ease}
.pulse-stack-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.pulse-stack-time{font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:#8A93A1;white-space:nowrap;font-variant-numeric:tabular-nums}
.pulse-stack-time .pulse-rel{font-size:10.5px;color:#8A93A1}
.pulse-stack-time .pulse-rel:not([hidden])+.pulse-abs{display:none}
.pulse-stack-title{margin:11px 0 8px;font-family:Poppins,sans-serif;font-size:17px;font-weight:600;letter-spacing:-.025em;line-height:1.3;color:#10141C;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.pulse-stackcard .pulse-body{font-size:13.5px;line-height:1.62}
.pulse-body-clamp2 p:first-child{-webkit-line-clamp:2;line-clamp:2}
.pulse-stack-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:auto;padding-top:13px;border-top:1px solid rgba(16,20,28,0.07);min-width:0}
.pulse-stack-foot .pulse-who-text{font-size:11.5px}
.pulse-stack-foot .pulse-proj{font-size:11.5px}
.pulse-stack-foot .pulse-role{font-size:11.5px;padding:2px 9px}

/* The dots say what kind of motion this is: four positions, one on top, the
   active one filling over the dwell. Without them a card sliding away could be
   read as an entry arriving, which would be a lie. */
.pulse-dots{display:flex;align-items:center;gap:7px;margin-top:18px}
.pulse-dots[hidden]{display:none}
.pulse-dot{position:relative;width:26px;height:5px;padding:0;border:0;border-radius:3px;background:rgba(16,20,28,0.11);cursor:pointer;overflow:hidden;transition:background .2s ease}
.pulse-dot:hover{background:rgba(16,20,28,0.2)}
.pulse-dot-fill{position:absolute;inset:0;transform-origin:left;transform:scaleX(0);background:#2E6BFF;border-radius:3px;display:block}
.pulse-dot[aria-current="true"]{width:44px}
.pulse-dot[aria-current="true"] .pulse-dot-fill{animation:pulseDwell 5500ms linear forwards}
.pulse-stackwrap.is-paused .pulse-dot-fill{animation-play-state:paused}
@keyframes pulseDwell{from{transform:scaleX(0)}to{transform:scaleX(1)}}

@media (max-width:980px){
  .pulse-overview-grid{grid-template-columns:1fr;gap:36px}
  .pulse-overview-copy p{max-width:none}
  /* One column now, so the stack sits under the copy rather than beside it. Cap
     its width anyway: a 700px-wide card two lines deep is mostly empty card. */
  .pulse-stackwrap{justify-self:start;width:min(100%,540px);--card-h:196px}
}
@media (max-width:520px){
  .pulse-overview{padding-bottom:96px}
  .pulse-stackwrap{--card-h:214px}
  .pulse-stackcard>article{padding:17px 17px;border-radius:14px}
  .pulse-stack-title{font-size:16px}
  .pulse-stack-foot{flex-wrap:wrap;gap:8px}
}
/* Reduced motion gets the same four entries, unstacked and complete, rather
   than a stack with three cards faded out under the top one and nothing to move
   them. app.js never starts the timer here, so the dots are never built. */
@media (prefers-reduced-motion: reduce){
  .pulse-stack{position:static;height:auto;display:grid;gap:10px}
  .pulse-stackcard{position:static;transform:none!important;opacity:1!important}
  .pulse-stackcard>article{height:auto;min-height:0}
  .pulse-livedot{opacity:1}
}

/* The range control. A rail rather than a button group: the labels sit on the
   same hairline the feed's own rail is drawn in, and the active one is marked
   by the accent underline plus ink weight, so it doesn't read as chrome
   borrowed from a framework. */
.pulse-controls{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:10px 20px;max-width:900px;margin:0 0 30px;border-bottom:1px solid rgba(16,20,28,0.11)}
.pulse-controls[hidden]{display:none}
.pulse-ranges{display:flex;flex-wrap:wrap;gap:2px}
.pulse-range{position:relative;border:0;background:none;cursor:pointer;padding:9px 13px 11px;font-family:'IBM Plex Mono',monospace;font-size:11.5px;letter-spacing:.06em;color:#8A93A1;border-radius:8px 8px 0 0}
.pulse-range:hover{color:#10141C;background:rgba(16,20,28,0.035)}
.pulse-range::after{content:"";position:absolute;left:13px;right:13px;bottom:-1px;height:2px;border-radius:2px;background:#2E6BFF;transform:scaleX(0);transform-origin:center;transition:transform .22s cubic-bezier(.2,.7,.2,1)}
.pulse-range[aria-pressed="true"]{color:#10141C}
.pulse-range[aria-pressed="true"]::after{transform:scaleX(1)}
.pulse-count{margin:0 0 11px;font-family:'IBM Plex Mono',monospace;font-size:11.5px;color:#8A93A1;font-variant-numeric:tabular-nums}
.pulse-empty{max-width:900px;margin:-8px 0 26px;font-size:14.5px;line-height:1.65;color:#6C7585}
.pulse-empty[hidden]{display:none}
.pulse-empty b{color:#10141C;font-weight:500}
/* Below the tablet break the gutter would eat a fifth of the line length, so
   the rail moves to the page edge and the day marker and time stack above the
   card. Same rail, same order, still anchored -- just not a two-column grid. */
@media (max-width:860px){
  .pulse-feed,.pulse-controls,.pulse-empty{max-width:none}
  .pulse-daymark,.pulse-entry{grid-template-columns:1fr;column-gap:0;padding-left:26px}
  .pulse-daymark::before,.pulse-entry::before{left:3px}
  .pulse-feed>.pulse-railhead::before{top:6px}
  .pulse-feed>.pulse-railtail::before{height:18px}
  .pulse-daymark{padding-bottom:8px}
  .pulse-daymark span{padding-top:0;padding-right:0;text-align:left}
  .pulse-entry{padding-bottom:26px}
  .pulse-entry::after{left:3px;top:16px}
  /* The gutter would eat a fifth of the line length here, so the two halves of
     the stamp go back onto one line above the card with a dot between them --
     the dot only when app.js has actually filled the relative half in. */
  .pulse-meta{padding-top:7px;padding-right:0;margin-bottom:9px}
  .pulse-time{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:0;text-align:left}
  .pulse-rel:not([hidden])+.pulse-abs::before{content:"·";margin:0 6px;color:#B4BBC6}
  .pulse-card{padding:18px 18px}
  /* Right-aligning the project chip only works while it shares a line with the
     kind mark. Once the row wraps it would sit alone against the right edge, so
     it goes back to reading as the next thing along. */
  .pulse-kindrow .pulse-proj{margin-left:0}
  .pulse-projects{max-width:none;gap:6px}
  .pulse-projbtn{font-size:12px;padding:6px 11px}
  .pulse-title{font-size:18.5px;margin:9px 0 10px}
  .pulse-body{font-size:14.5px;line-height:1.72}
  .pulse-foot{margin-top:14px;padding-top:12px;gap:9px 14px}
  /* Stacked, the count would land between the labels and the rule and leave the
     active underline floating off the border it belongs to. Reversing the
     column puts the count above and keeps the labels sitting on the line. */
  .pulse-controls{flex-direction:column-reverse;align-items:stretch;gap:0}
  .pulse-count{margin:0 0 9px}
  .pulse-range{padding:9px 10px 11px;font-size:11px}
  .pulse-range::after{left:10px;right:10px}
}
@media (max-width:400px){
  .pulse-ranges{gap:0}
  .pulse-range{padding:9px 7px 11px;letter-spacing:.02em}
  .pulse-range::after{left:7px;right:7px}
}
