/* ============================================================
   BUILDS: tools I designed and built, shared as code
   Homepage: full-width rows under a heading, each with a small diagram of
   what the tool does (not a screenshot), a line, and two actions: the page
   that explains it, and the source. Build pages reuse the buttons in the
   head (css/case-template.css keeps the rest of the layout).
   ============================================================ */

.builds-head{
  display:grid;gap:clamp(10px,1.2vw,16px);
  margin-bottom:clamp(24px,3vw,40px);
}
@media (min-width:900px){
  .builds-head{grid-template-columns:minmax(0,7fr) minmax(0,5fr);column-gap:clamp(40px,5vw,80px);align-items:end;}
}
.builds-title{
  font-family:var(--display);font-weight:500;
  font-size:clamp(1.625rem, 1.2rem + 1.6vw, 2.625rem);   /* 26 -> 42 */
  line-height:1.15;letter-spacing:-0.02em;color:var(--ink);margin:0;text-wrap:balance;
}
.builds-intro{
  font-family:var(--body);font-size:var(--t-body-size);line-height:1.5;color:var(--ink-2);margin:0;max-width:40ch;
}

/* ---------- rows ----------
   The rows live in the projects' ruled grid (.cases-b, js/grid-draw.js) and
   take the same hover: the white fill and the selection box with the stack
   as its label (js/project-select.js). One column at every width. */
.cases-b.builds{grid-template-columns:minmax(0,1fr);}
.cases-b > li.build{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:clamp(16px,2vw,32px);align-items:center;
  padding:clamp(16px,1.8vw,24px) var(--cell-pad);
  transition:background-color .2s ease;
}
@media (min-width:900px){
  .cases-b > li.build{grid-template-columns:auto minmax(0,1fr) auto;}
}
@media (hover:hover){ .cases-b > li.build:hover{background:var(--paper);} }
.cases-b > li.build:has(:focus-visible){background:var(--paper);}
@media (hover:hover){ :root[data-theme="dark"] .cases-b > li.build:hover{background:var(--cell-hover);} }
:root[data-theme="dark"] .cases-b > li.build:has(:focus-visible){background:var(--cell-hover);}

.build-art{
  width:clamp(120px,16vw,220px);aspect-ratio:12/7;
  background:var(--case-field);border-radius:clamp(6px,0.6vw,10px);
  display:grid;place-items:center;overflow:hidden;
}
.build-art svg{width:100%;height:100%;display:block;overflow:visible;color:var(--accent);}   /* currentColor: the lit cards in the graph story */

.build-text{min-width:0;}
.build-stack{
  font-family:var(--mono);font-size:12px;line-height:1.4;letter-spacing:0;color:var(--muted);margin:0 0 6px;
}
.build h3{
  font-family:var(--body);font-weight:500;
  font-size:clamp(1.0625rem,0.95rem + 0.5vw,1.375rem);   /* 17 -> 22 */
  line-height:1.25;letter-spacing:-0.018em;color:var(--ink);margin:0;
}
.build-link{color:inherit;text-decoration:none;}
/* the whole row opens the page; the buttons sit above this layer */
.build-link::after{content:"";position:absolute;inset:0;z-index:1;}
.build-link:focus-visible{outline:none;}
/* the title link covers the row, so the row shows its focus, like a project card */
.cases-b > li.build:has(.build-link:focus-visible){background:var(--paper);outline:2px solid var(--accent);outline-offset:-2px;}
:root[data-theme="dark"] .cases-b > li.build:has(.build-link:focus-visible){background:var(--cell-hover);}
.build-line{
  font-family:var(--body);font-size:var(--t-sm-size);line-height:1.45;color:var(--ink-2);
  margin:6px 0 0;max-width:60ch;
}

/* pointing at GitHub leaves the row: that button goes somewhere else, so the
   row drops its fill, selection and "How it works" (the diagram keeps playing) */
:root .cases-b > li.build:has(.code-btn.is-dark:hover){background:transparent;}
.cases-b > li.build:has(.code-btn.is-dark:hover) .shot-sel{opacity:0;}
.build:has(.code-btn.is-dark:hover) .code-btn.is-more{color:var(--ink);}
.build:has(.code-btn.is-dark:hover) .code-btn.is-more .arrow{transform:none;}

.build-actions{
  position:relative;z-index:2;
  display:flex;flex-wrap:wrap;gap:8px;
  grid-column:1 / -1;
}
@media (min-width:900px){ .build-actions{grid-column:auto;justify-content:flex-end;} }

/* ---------- buttons (homepage rows and build page heads) ---------- */
.code-btn{
  display:inline-flex;align-items:center;gap:8px;
  min-height:40px;padding:0 16px;border-radius:11px;
  font-family:var(--body);font-weight:500;font-size:var(--t-sm-size);line-height:1;
  text-decoration:none;white-space:nowrap;
  background:var(--paper);color:var(--ink);border:1px solid var(--rule);
  transition:background-color var(--hover-t, .1s) ease,color var(--hover-t, .1s) ease,border-color var(--hover-t, .1s) ease;
}
.code-btn svg{width:16px;height:16px;flex:none;fill:currentColor;}
.code-btn.is-dark{background:var(--ink);color:var(--paper);border-color:var(--ink);}
@media (hover:hover){
  .code-btn:not(.is-more):hover{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);color:var(--accent-text);}
  .code-btn.is-dark:hover{background:var(--accent-text);border-color:var(--accent-text);box-shadow:none;color:#fff;}
}
/* "How it works" is the row's own link, so it reads as text, not a button:
   no fill or stroke, and it turns blue with the row */
.code-btn.is-more{background:none;border-color:transparent;padding-inline:8px;}
@media (hover:hover){ .build:hover .code-btn.is-more{color:var(--accent-text);} }
.build:has(.build-link:focus-visible) .code-btn.is-more{color:var(--accent-text);}
.code-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.code-btn .arrow{display:inline-block;transition:transform .2s ease;}
@media (hover:hover){ .build:hover .code-btn.is-more .arrow{transform:translateX(3px);} }

/* build page head: the buttons sit under the standfirst */
.cs-actions{display:flex;flex-wrap:wrap;gap:10px;margin:clamp(20px,2.4vw,28px) 0 0;}
@media (min-width:1100px){
  .cs-summary.is-editorial .cs-actions{grid-column:2;align-self:start;}
  /* the title spans both rows, so the buttons follow the standfirst directly */
  .cs-summary.is-editorial:has(.cs-actions) .cs-title{grid-row:span 2;}
}

/* ---------- diagrams ----------
   Drawn in the site's tokens, so they follow the theme. Still at rest; the
   story plays on hover, or once when the row scrolls into view on touch
   screens (.is-live, js/builds.js). Reduced motion: no story. */
.sk-card{fill:var(--paper);stroke:var(--ink-2);stroke-opacity:.35;stroke-width:1;}
.sk-card.is-warn{stroke-dasharray:3 2;stroke-opacity:.6;}
/* the loose screen sits a little askew until it is dropped into the gap */
.sk-loose{transform-box:fill-box;transform-origin:center;transform:rotate(-7deg);}
.sk-bar{fill:var(--ink-2);opacity:.22;}
.sk-line{fill:none;stroke:var(--ink-2);stroke-opacity:.45;stroke-width:1.2;stroke-linejoin:round;stroke-linecap:round;}
.sk-line.is-dash{stroke-dasharray:4 3;}
.sk-line.is-dot{stroke-dasharray:0.1 3.2;stroke-width:1.8;}
.sk-dot{fill:var(--ink-2);opacity:.5;}
.sk-node{transform-box:fill-box;transform-origin:center;}
.sk-slot{fill:none;stroke:var(--ink-2);stroke-opacity:.45;stroke-width:1;stroke-dasharray:3 2.5;}
.sk-join{fill:none;stroke:var(--ink-2);stroke-opacity:.45;stroke-width:1.2;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1;}
.sk-loose-card{opacity:0;}
.sk-trail{fill:none;stroke:var(--accent);stroke-width:1.8;stroke-linejoin:round;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:1;}
.sk-runner{fill:var(--accent);opacity:0;}
@supports (offset-path:path("M0 0")){
  .sk-runner{offset-path:path("M34 70H66V42H191V70H216");offset-distance:0%;offset-rotate:0deg;}
}
@supports not (offset-path:path("M0 0")){ .sk-runner{display:none;} }

.sk-target{fill:var(--ink-2);opacity:.22;}
.sk-chev{fill:none;stroke:var(--ink-2);stroke-opacity:.3;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;}
.sk-submit{fill:var(--ink-2);opacity:.22;}
.sk-submit-lit{fill:var(--accent);opacity:0;}
.sk-ripple{fill:none;stroke:var(--accent);stroke-width:1.2;opacity:0;transform-box:fill-box;transform-origin:center;}
.sk-pin{opacity:0;transform-box:fill-box;transform-origin:50% 100%;}
.sk-pin circle:first-child{fill:var(--accent);}
.sk-pin-core{fill:var(--paper);}
.sk-new{opacity:0;}
.sk-new-avatar{fill:var(--accent);}
.sk-type{fill:var(--ink-2);opacity:.35;transform-box:fill-box;transform-origin:left center;}
.sk-gh{fill:var(--ink-2);opacity:.75;color:var(--ink);}   /* color: what it lights up to (sk-lit) */
.sk-gh path{transform-box:fill-box;transform-origin:center;}
.sk-row-new{opacity:0;transform-box:fill-box;transform-origin:center;}
.sk-row-box{fill:var(--paper);stroke:var(--accent);stroke-width:1.4;}
.sk-cursor{fill:var(--ink-2);stroke:var(--ink-2);stroke-width:1;stroke-linejoin:round;opacity:0;transform:translate(176px,122px);}

@media (prefers-reduced-motion:no-preference){
  /* graph: the cursor drags the loose screen into the gap, the links draw
     in, then a signal runs the chain and lights each screen as it passes */
  :is(.build:hover,.build.is-live) .sk-cursor-drag{animation:sk-grab 1s cubic-bezier(.4,0,.2,1) forwards;}
  :is(.build:hover,.build.is-live) .sk-loose{animation:sk-carry .5s cubic-bezier(.4,0,.2,1) .34s forwards;}
  :is(.build:hover,.build.is-live) .sk-loose .is-warn{animation:sk-out .2s ease .8s forwards;}
  :is(.build:hover,.build.is-live) .sk-loose-card{animation:sk-in .2s ease .8s forwards;}
  :is(.build:hover,.build.is-live) .sk-slot{animation:sk-out .15s ease .82s forwards;}
  :is(.build:hover,.build.is-live) .sk-join{animation:sk-draw .25s ease .88s forwards;}
  :is(.build:hover,.build.is-live) .sk-trail{animation:sk-draw 1s cubic-bezier(.45,0,.3,1) 1.15s forwards;}
  :is(.build:hover,.build.is-live) .sk-runner{animation:sk-run 1s cubic-bezier(.45,0,.3,1) 1.15s forwards;}
  :is(.build:hover,.build.is-live) :is(.sk-a,.sk-b,.sk-f) .sk-card,
  :is(.build:hover,.build.is-live) .sk-loose-card{animation-name:sk-hit;animation-duration:.4s;animation-fill-mode:forwards;}
  :is(.build:hover,.build.is-live) .sk-loose-card{animation:sk-in .2s ease .8s forwards,sk-hit .4s ease 1.72s forwards;}
  :is(.build:hover,.build.is-live) :is(.sk-a,.sk-b,.sk-f){animation:sk-bump .45s ease;}
  :is(.build:hover,.build.is-live) .sk-a,:is(.build:hover,.build.is-live) .sk-a .sk-card{animation-delay:1.15s;}
  :is(.build:hover,.build.is-live) .sk-b,:is(.build:hover,.build.is-live) .sk-b .sk-card{animation-delay:1.42s;}
  :is(.build:hover,.build.is-live) .sk-f,:is(.build:hover,.build.is-live) .sk-f .sk-card{animation-delay:2.08s;}

  /* comments: the cursor clicks a button, a pin drops, a comment types into
     the panel, and it lands on top of the repository's discussions */
  :is(.build:hover,.build.is-live) .sk-cursor:not(.sk-cursor-drag){animation:sk-cursor 1.7s cubic-bezier(.3,.7,.3,1) forwards;}
  :is(.build:hover,.build.is-live) .sk-ripple{animation:sk-ripple .4s ease-out .38s;}
  :is(.build:hover,.build.is-live) .sk-pin{animation:sk-drop .35s cubic-bezier(.34,1.56,.64,1) .46s forwards;}
  :is(.build:hover,.build.is-live) .sk-new{animation:sk-in .2s ease .62s forwards;}
  :is(.build:hover,.build.is-live) .sk-type-1{animation:sk-type .3s steps(6) .66s both;}
  :is(.build:hover,.build.is-live) .sk-type-2{animation:sk-type .22s steps(4) .94s both;}
  :is(.build:hover,.build.is-live) .sk-ripple-2{animation:sk-ripple .4s ease-out 1.26s;}
  :is(.build:hover,.build.is-live) .sk-submit-lit{animation:sk-flash .5s ease 1.24s;}
  :is(.build:hover,.build.is-live) .sk-chev{animation:sk-chev .4s ease 1.3s forwards;}
  :is(.build:hover,.build.is-live) .sk-rows{animation:sk-shift .28s cubic-bezier(.3,.7,.3,1) 1.3s forwards;}
  :is(.build:hover,.build.is-live) .sk-row-new{animation:sk-pop .32s cubic-bezier(.34,1.56,.64,1) 1.38s forwards;}
  :is(.build:hover,.build.is-live) .sk-gh path{animation:sk-nod .4s cubic-bezier(.34,1.56,.64,1) 1.44s;}
  :is(.build:hover,.build.is-live) .sk-gh{animation:sk-lit .25s ease 1.44s forwards;}
}

@keyframes sk-draw{to{stroke-dashoffset:0;}}
@keyframes sk-run{0%{opacity:1;offset-distance:0%;}92%{opacity:1;}100%{opacity:0;offset-distance:100%;}}
@keyframes sk-hit{to{stroke:currentColor;stroke-opacity:1;stroke-width:1.4;}}
@keyframes sk-bump{40%{transform:translateY(-2.5px);}}
@keyframes sk-grab{
  0%{opacity:0;transform:translate(236px,138px);}
  12%{opacity:1;}
  30%{transform:translate(212px,118px) scale(1);}
  34%{transform:translate(212px,118px) scale(.8);}
  84%{opacity:1;transform:translate(164px,44px) scale(.8);}
  90%{transform:translate(164px,44px) scale(1);}
  100%{opacity:0;transform:translate(170px,56px);}
}
@keyframes sk-carry{to{transform:translate(-48px,-74px) rotate(0deg);}}
@keyframes sk-out{to{opacity:0;}}
@keyframes sk-cursor{
  0%{opacity:0;transform:translate(176px,122px);}
  8%{opacity:1;}
  23%{transform:translate(34px,78px) scale(1);}
  26%{transform:translate(34px,78px) scale(.8);}
  30%{transform:translate(34px,78px) scale(1);}
  46%{transform:translate(34px,78px);}
  70%{transform:translate(119px,105px) scale(1);}
  73%{transform:translate(119px,105px) scale(.8);}
  77%{opacity:1;transform:translate(119px,105px) scale(1);}
  100%{opacity:0;transform:translate(126px,118px);}
}
@keyframes sk-chev{
  40%{stroke:currentColor;stroke-opacity:1;transform:translateX(3px);}
  100%{stroke:currentColor;stroke-opacity:1;transform:none;}
}
@keyframes sk-flash{0%{opacity:0;}25%{opacity:1;}100%{opacity:0;}}
}
@keyframes sk-ripple{0%{opacity:.9;transform:scale(1);}100%{opacity:0;transform:scale(3.2);}}
@keyframes sk-drop{0%{opacity:0;transform:translateY(-8px) scale(.4);}100%{opacity:1;transform:none;}}
@keyframes sk-in{from{opacity:0;}to{opacity:1;}}
@keyframes sk-type{from{transform:scaleX(0);}to{transform:scaleX(1);}}
@keyframes sk-nod{50%{transform:scale(1.25);}}
@keyframes sk-lit{to{opacity:1;fill:currentColor;}}
@keyframes sk-shift{to{transform:translateY(23px);}}
@keyframes sk-pop{0%{opacity:0;transform:scale(.7);}100%{opacity:1;transform:none;}}

/* phones: the diagram leads, text and buttons below */
@media (max-width:599px){
  .cases-b > li.build{grid-template-columns:minmax(0,1fr);}
  .build-art{display:block;width:100%;aspect-ratio:auto;height:140px;padding:8px 0;}
  .build-art svg{width:100%;height:100%;}
  .build-actions{justify-content:space-between;align-items:center;}
  .build-actions .code-btn.is-more{padding-inline:0;}
}

/* ---------- live demo in a build page hero (js/demo.js) ----------
   The iframe sits on the hero image in the same field and takes over once
   loaded. The bar under it says it is live and what to try. */
.case-media.has-demo > *{grid-area:1 / 1;}
.case-media.has-demo > .demo-frame{position:relative;z-index:2;}
.demo-frame{
  /* the same edge as the screenshot it replaces (css/tiers.css .case-media img) */
  width:100%;height:100%;display:block;
  border:1px solid var(--rule);border-radius:clamp(6px,0.6vw,10px);
  background:var(--paper);
  opacity:0;visibility:hidden;transition:opacity .45s ease,visibility 0s linear .45s;
}
[data-demo].is-live .demo-frame{opacity:1;visibility:visible;transition:opacity .45s ease;}
.demo-bar{
  display:none;align-items:center;gap:12px;flex-wrap:wrap;
  margin-bottom:12px;font-family:var(--body);font-size:var(--t-sm-size);color:var(--ink-2);
}
:is([data-demo].is-loading,[data-demo].is-live) .demo-bar{display:flex;}
[data-demo].is-loading .demo-hint{opacity:.5;}
.demo-tag{
  display:inline-flex;align-items:center;gap:7px;padding:4px 10px;border-radius:999px;
  font-weight:500;color:var(--ink);background:var(--paper);border:1px solid var(--rule);
}
.demo-tag i{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 0 3px rgba(34,197,94,.18);}
/* while loading, the badge's dot is a small spinner */
[data-demo].is-loading .demo-tag i{
  background:none;box-shadow:none;width:10px;height:10px;
  border:1.6px solid var(--rule);border-top-color:var(--accent);
  animation:demo-spin .8s linear infinite;
}

/* while loading, the screenshot dims and a spinner sits on it */
.case-media.has-demo > img{transition:opacity .3s ease,filter .3s ease;}
[data-demo].is-loading .case-media.has-demo > img{opacity:.55;filter:grayscale(.4) blur(1.5px);}
.demo-loader{
  position:relative;z-index:3;place-self:center;
  display:inline-flex;align-items:center;gap:12px;
  padding:12px 20px 12px 16px;border-radius:999px;
  font-family:var(--body);font-weight:500;font-size:var(--t-body-size);color:var(--ink);
  background:var(--paper);border:1px solid var(--rule);
  box-shadow:0 12px 32px rgba(8,14,26,.16), 0 1px 3px rgba(8,14,26,.10);
  opacity:0;visibility:hidden;transform:translateY(6px) scale(.97);
  transition:opacity .3s ease,transform .3s ease,visibility 0s linear .3s;
}
[data-demo].is-loading .demo-loader{opacity:1;visibility:visible;transform:none;transition:opacity .3s ease,transform .3s ease;}
.demo-spin{
  width:20px;height:20px;border-radius:50%;
  border:2.2px solid var(--rule);border-top-color:var(--accent);
  animation:demo-spin .8s linear infinite;
}
@keyframes demo-spin{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){
  [data-demo].is-loading .demo-tag i,.demo-spin{animation-duration:2.4s;}
}
.demo-full{margin-left:auto;color:var(--ink);text-decoration:none;font-weight:500;}
@media (hover:hover){ .demo-full:hover{color:var(--accent-text);} }

/* dark: the case fields are almost the page's own grey there. The build page
   heroes take a lighter field and a hairline. The diagrams drop the field
   instead: a hairline frame on the page ground, and cards lifted above it
   (lighter fill, brighter edge), the way light mode's white cards sit on grey */
:root[data-theme="dark"] [data-demo] .case-media{
  background:#1f1f24;box-shadow:inset 0 0 0 1px var(--rule);
}
:root[data-theme="dark"] .build-art{background:transparent;box-shadow:inset 0 0 0 1px var(--rule);}
:root[data-theme="dark"] .sk-card,
:root[data-theme="dark"] .sk-row-box{fill:#1c1c21;stroke:#52525c;stroke-opacity:1;}
:root[data-theme="dark"] .sk-bar{fill:var(--ink);opacity:.2;}
:root[data-theme="dark"] .sk-line,
:root[data-theme="dark"] .sk-join{stroke:#71717b;stroke-opacity:1;}
:root[data-theme="dark"] .sk-slot{stroke:#71717b;stroke-opacity:1;}
:root[data-theme="dark"] .sk-dot{fill:var(--ink);opacity:.35;}
:root[data-theme="dark"] .sk-target,
:root[data-theme="dark"] .sk-submit{fill:var(--ink);opacity:.18;}
:root[data-theme="dark"] .sk-chev{stroke:#71717b;stroke-opacity:1;}
:root[data-theme="dark"] .sk-gh{fill:#a1a1aa;}
:root[data-theme="dark"] .sk-cursor{fill:var(--ink);stroke:var(--ink);}
:root[data-theme="dark"] .sk-pin-core{fill:#1c1c21;}

/* dark, hovered row: the row lifts to --cell-hover, which is lighter than the
   cards above, so the diagram lifts with it: lighter cards, brighter edges
   and lines, a visible frame */
@media (hover:hover){
  :root[data-theme="dark"] .build:hover .build-art{box-shadow:inset 0 0 0 1px var(--rule-2);}
  :root[data-theme="dark"] .build:hover :is(.sk-card,.sk-row-box){fill:#2c2c33;stroke:#83838d;}
  :root[data-theme="dark"] .build:hover .sk-bar{opacity:.32;}
  :root[data-theme="dark"] .build:hover :is(.sk-line,.sk-join,.sk-slot,.sk-chev){stroke:#9f9fa9;}
  :root[data-theme="dark"] .build:hover .sk-dot{opacity:.55;}
  :root[data-theme="dark"] .build:hover :is(.sk-target,.sk-submit){opacity:.3;}
  :root[data-theme="dark"] .build:hover .sk-gh{fill:#d4d4d8;}
  :root[data-theme="dark"] .build:hover .sk-pin-core{fill:#2c2c33;}
}
