/* bhavish.ca shared design layer
   Extracted from the Transborder Retreat feature so the site chrome and the
   published pieces read as one product. Flat editorial: ruled, not rounded.
   No border-radius, no shadows - both are deliberate. */

:root{
  --ink:#131A22; --paper:#FCFCFD; --panel:#F2F4F7; --rule:#D9DFE7;
  --muted:#66727F; --signal:#1F4FD8; --lift:#C97609; --alert:#A32F2F;
  --body:#2C3742; --hair:#EAEEF3;
  --mono:"IBM Plex Mono",ui-monospace,monospace;
  --sans:"IBM Plex Sans",system-ui,sans-serif;
  --display:"Archivo",var(--sans);
  --wrap:1060px; --gutter:28px;
  /* categorical series, used by the pipeline figure; overridden in dark.css */
  --s1:#1F4FD8; --s2:#2FA95F; --s3:#C97609; --s4:#A32F2F;
  --s5:#147D8D; --s6:#6B4BA8; --s7:#4A6076;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--paper);color:var(--ink);
  font-family:var(--sans);font-size:16.5px;line-height:1.62;-webkit-font-smoothing:antialiased}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--gutter)}
a{color:var(--signal)}
:focus-visible{outline:2px solid var(--signal);outline-offset:2px}

/* ---- shared primitives (same names as the feature pages) ---- */
.eyebrow{font-family:var(--mono);font-size:11.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);display:flex;gap:18px;flex-wrap:wrap}
.eyebrow b{color:var(--signal);font-weight:600}
h1{font-family:var(--display);font-weight:700;font-size:clamp(2rem,5.2vw,3.4rem);
  line-height:1.04;letter-spacing:-.022em;margin:.4em 0 .3em;max-width:21ch}
h2{font-family:var(--display);font-weight:600;letter-spacing:-.015em;
  font-size:clamp(1.4rem,3.2vw,2rem);margin:0;line-height:1.15}
h3{font-family:var(--display);font-size:1.05rem;font-weight:600;margin:30px 0 6px}
p{max-width:66ch}
.standfirst{font-size:1.12rem;max-width:63ch;color:var(--body);margin:0 0 22px}
.lede{max-width:64ch;color:var(--body)}

/* ---- site chrome ---- */
header.site{border-bottom:2px solid var(--ink);padding:26px 0 18px;
  display:flex;justify-content:space-between;align-items:baseline;gap:20px;flex-wrap:wrap}
.brand{font-family:var(--display);font-weight:700;font-size:1.15rem;
  letter-spacing:-.015em;color:var(--ink);text-decoration:none}
.brand span{color:var(--signal)}
nav.site{font-family:var(--mono);font-size:11.5px;letter-spacing:.09em;
  text-transform:uppercase;display:flex;gap:20px;flex-wrap:wrap}
nav.site a{color:var(--muted);text-decoration:none}
nav.site a:hover,nav.site a[aria-current]{color:var(--signal)}

.hero{padding:56px 0 40px;border-bottom:1px solid var(--rule)}
.approach{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;
  margin:34px 0 4px;padding-top:26px;border-top:1px solid var(--rule)}
.approach h3{font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;
  text-transform:uppercase;margin:0 0 7px;font-weight:600}
.approach p{font-size:14.5px;color:var(--muted);margin:0;max-width:34ch}

section.band{border-top:1px solid var(--rule);padding:52px 0 8px}
.sec-head{display:flex;gap:20px;align-items:baseline;margin-bottom:22px}
.sec-num{font-family:var(--mono);font-size:12px;font-weight:600;color:var(--signal);
  letter-spacing:.1em;padding-top:6px;flex:none}

/* ---- work cards ---- */
.cards{display:grid;gap:0;margin:8px 0 12px}
.card{display:grid;grid-template-columns:132px 1fr;gap:26px;
  padding:26px 0;border-bottom:1px solid var(--rule);text-decoration:none;color:inherit}
.card:first-child{border-top:1px solid var(--rule)}
/* was a hardcoded near-white, which read as a stark white slab in dark mode */
.card:hover{background:var(--panel)}
.card:hover .card-title{color:var(--signal)}
.card-meta{font-family:var(--mono);font-size:11.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted);line-height:1.9}
.card-title{font-family:var(--display);font-size:1.3rem;font-weight:600;
  letter-spacing:-.015em;line-height:1.2;margin:0 0 4px;max-width:26ch}
.card-deck{font-family:var(--display);font-size:1rem;font-weight:500;
  color:var(--muted);margin:0 0 10px;max-width:44ch}
.card-summary{font-size:15px;color:var(--body);margin:0 0 12px;max-width:62ch}
.tags{display:flex;gap:7px;flex-wrap:wrap}
.tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  padding:3px 8px;border:1px solid var(--rule);color:var(--muted)}
.card:hover .tag{border-color:var(--rule)}
.status-flag{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;padding:3px 8px;border:1px solid var(--lift);color:var(--lift)}

.empty{font-family:var(--mono);font-size:13px;color:var(--muted);
  border:1px dashed var(--rule);padding:30px;text-align:center}

footer.site{border-top:2px solid var(--ink);margin-top:56px;padding:26px 0 70px;
  font-family:var(--mono);font-size:12px;color:var(--muted);
  display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
footer.site a{color:var(--signal)}

@media (max-width:760px){
  .approach{grid-template-columns:1fr;gap:22px}
  .card{grid-template-columns:1fr;gap:10px}
  .sec-head{flex-direction:column;gap:2px}
  .sec-num{padding-top:0}
}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

/* ---- contact form (same flat idiom as the feature console) ---- */
.form{max-width:56ch;margin:26px 0 8px}
.field{margin:0 0 18px}
.field label{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);margin:0 0 6px}
.field input,.field textarea{width:100%;font-family:var(--sans);font-size:15px;
  color:var(--ink);background:var(--paper);border:1px solid var(--rule);padding:10px 12px}
.field textarea{min-height:150px;resize:vertical;line-height:1.55}
.field input:focus-visible,.field textarea:focus-visible{outline:2px solid var(--signal);outline-offset:1px}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
button.send{font-family:var(--mono);font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;
  background:var(--ink);color:var(--paper);border:1px solid var(--ink);padding:11px 22px;cursor:pointer}
button.send:hover:not(:disabled){background:var(--signal);border-color:var(--signal)}
button.send:disabled{opacity:.45;cursor:default}
.form-note{font-family:var(--mono);font-size:11.5px;color:var(--muted);margin:14px 0 0;max-width:52ch;line-height:1.7}
.form-msg{font-family:var(--mono);font-size:12.5px;margin:16px 0 0;padding:12px 14px;border:1px solid var(--rule)}
.form-msg.ok{color:var(--signal);border-color:var(--signal)}
.form-msg.err{color:var(--alert);border-color:var(--alert)}
.alt-contact{border-top:1px solid var(--rule);margin-top:36px;padding-top:22px}
button.reveal{font-family:var(--mono);font-size:12px;background:var(--paper);color:var(--signal);
  border:1px solid var(--rule);padding:8px 13px;cursor:pointer}
button.reveal:hover{border-color:var(--signal)}

/* ---- curriculum vitae ----
   The DOM is deliberately LINEAR (role, employer+location, dates, bullets) because
   ATS parsers extract PDF text in document order and interleave anything laid out
   in columns. The left-hand date rail on screen is pure grid placement over that
   linear source; print drops to plain block flow. */
.cv{max-width:none}
.cv-head{border-bottom:2px solid var(--ink);padding-bottom:16px}
.cv-head h1{font-size:clamp(1.9rem,4.4vw,2.7rem);margin:.1em 0 .1em;max-width:none}
.cv-role{font-family:var(--display);font-size:1.08rem;font-weight:500;color:var(--muted);margin:0 0 12px}
.cv-contact{font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;color:var(--muted);
  display:flex;gap:18px;flex-wrap:wrap;justify-content:space-between}
.cv-contact a{color:var(--signal);text-decoration:none}
.cv-actions{display:flex;gap:10px;flex-wrap:wrap;padding:18px 0 0}
button.print{font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase;
  background:var(--paper);color:var(--ink);border:1px solid var(--rule);padding:9px 15px;cursor:pointer}
button.print:hover{border-color:var(--signal);color:var(--signal)}

.cv-block{border-top:1px solid var(--rule);padding:24px 0 4px;margin-top:22px}
.cv-block > h2{font-family:var(--mono);font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--signal);font-weight:600;margin:0 0 18px}
.cv-summary{padding-left:17px;margin:0;max-width:80ch}
.cv-summary li{font-size:15px;line-height:1.5;margin:0 0 5px}

/* Employer and dates share one row: same DOM order, one less line per entry.
   Two items on a single baseline extract left-to-right in document order, which
   is the pattern parsers expect - unlike a true sidebar column, which interleaves. */
.cv-entry{padding:0 0 18px}
.cv-entry-line{display:grid;grid-template-columns:1fr auto;column-gap:20px;align-items:baseline}
.cv-entry h3{font-family:var(--display);font-size:1.08rem;font-weight:600;margin:0 0 1px}
.cv-entry .cv-org{font-size:14.6px;color:var(--body);margin:0 0 8px}
.cv-entry .cv-dates{text-align:right;font-family:var(--mono);font-size:11.5px;
  letter-spacing:.04em;color:var(--muted);margin:0;white-space:nowrap}
.cv-entry ul{padding-left:17px;margin:0}
.cv-entry li{font-size:14.6px;line-height:1.52;margin:0 0 5px;max-width:74ch}
.cv-entry li::marker{color:var(--rule)}
.cv-cert{padding-bottom:11px}
.cv-cert .cv-org{margin:2px 0 0;color:var(--muted);font-size:13.5px}
.cv-skillline{font-size:14.6px;line-height:1.55;margin:0 0 7px;max-width:80ch}
.cv-skillline strong{font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted);font-weight:600}

@media (max-width:560px){
  .cv-entry-line{grid-template-columns:1fr}
  .cv-entry .cv-dates{text-align:left;margin:0 0 7px}
  .cv-entry .cv-org{margin:0 0 1px}
}


/* ---- gated cards + access prompts ---- */
.card{display:grid;grid-template-columns:132px 1fr;gap:26px;padding:26px 0;
  border-bottom:1px solid var(--rule)}
.card:first-child{border-top:1px solid var(--rule)}
.card-title a{color:inherit;text-decoration:none}
.card-title a:hover{color:var(--signal)}
.lock{font-size:.72em;margin-left:9px;opacity:.5;vertical-align:middle}
.card-highlights{margin:0 0 12px;padding-left:16px;max-width:62ch}
.card-highlights li{font-size:13.6px;line-height:1.5;color:var(--muted);margin:0 0 3px}
/* Card art sits behind the text, masked so it is absent where words are and
   present only at the outer edge. Low opacity and the signal hue keep it as
   texture rather than illustration - it should register after the reading,
   not compete with it. */
.card{position:relative}
.card > *{position:relative;z-index:1}
.card-art{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.card-art svg{
  position:absolute;right:-8px;top:50%;transform:translateY(-50%);
  height:124%;width:auto;color:var(--signal);opacity:.5;
  -webkit-mask-image:linear-gradient(to right,transparent 40%,rgba(0,0,0,.4) 68%,#000 100%);
  mask-image:linear-gradient(to right,transparent 40%,rgba(0,0,0,.4) 68%,#000 100%);
}
.card:hover .card-art svg{opacity:.72}
@media (prefers-reduced-motion:no-preference){
  .card-art svg{transition:opacity .35s ease}
}
@media (max-width:760px){.card-art{display:none}}

/* ---- route map ----
   Real airports, real relative positions, drawn from their coordinates. The
   dashes flow along the routes and one aircraft transits YVR to YYZ, so the
   card reads as a feed rather than a decal. Everything is one hue at low
   opacity: it sits behind the words and must never compete with them. */
.ca-routes path{fill:none;stroke:currentColor;stroke-width:1;opacity:.5}
.ca-arc{opacity:.55}
.ca-far{opacity:.3}
.ca-dash{stroke-dasharray:5 6;stroke-linecap:round}
.ca-dot{fill:currentColor;opacity:.9}
.ca-hub{opacity:1}
.ca-halo{fill:none;stroke:currentColor;stroke-width:1;opacity:0;
  transform-box:fill-box;transform-origin:center}
.ca-code{font-family:var(--mono);font-size:9px;letter-spacing:.09em;
  fill:currentColor;opacity:.62}
.ca-hubcode{opacity:.85;font-weight:600}
/* offset-rotate:auto aligns the element's +X axis with the path, but the
   aircraft is drawn nose-up, which is -90deg from +X - so it slid sideways
   along the arc. The extra 90deg puts the nose on the tangent, and it then
   banks through the curve on its own. */
.ca-plane{color:currentColor;offset-rotate:auto 90deg;offset-distance:0%;opacity:.7}

@media (prefers-reduced-motion:no-preference){
  .ca-dash{animation:ca-flow 2.4s linear infinite}
  .ca-slow{animation-duration:3.6s}
  .ca-halo{animation:ca-ping 3.4s ease-out infinite;animation-delay:var(--d,0s)}
  .ca-plane{animation:ca-fly 16s linear infinite}
}
@keyframes ca-flow{to{stroke-dashoffset:-22}}
@keyframes ca-ping{
  0%{opacity:.5;transform:scale(1)}
  55%{opacity:0;transform:scale(3.4)}
  100%{opacity:0;transform:scale(3.4)}
}
@keyframes ca-fly{
  0%{offset-distance:0%;opacity:0}
  6%{opacity:.7}
  92%{opacity:.7}
  100%{offset-distance:100%;opacity:0}
}

.glyph{font-size:.78em;margin-left:7px;vertical-align:middle;opacity:.85}

/* public card carries no synopsis, so the highlights are the substance and read larger */
.card--min .card-highlights{margin:2px 0 14px}
.card--min .card-highlights li{font-size:14.6px;color:var(--body);margin:0 0 5px}
.card-highlights li::marker{color:var(--rule)}
.card-actions{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;margin:14px 0 0}
a.req{font-family:var(--mono);font-size:11.5px;letter-spacing:.07em;text-transform:uppercase;
  text-decoration:none;color:var(--paper);background:var(--ink);border:1px solid var(--ink);
  padding:9px 15px;display:inline-block}
a.req:hover{background:var(--signal);border-color:var(--signal)}
.req-note{font-family:var(--mono);font-size:11px;color:var(--muted)}
.req-note a{color:var(--signal)}
.access-note{border:1px solid var(--rule);background:var(--panel);padding:20px 22px;
  margin:26px 0;max-width:70ch;font-size:14.5px;color:var(--body)}
.access-note strong{font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--signal);display:block;margin:0 0 7px}
.access-note p{margin:0}

/* résumé preview: deliberately spare. No employers, no responsibilities. */
.cv-preview .cv-block{padding:22px 0 4px}
.chips{display:flex;gap:8px;flex-wrap:wrap;margin:0}
.chip-item{font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  padding:5px 10px;border:1px solid var(--rule);color:var(--muted)}
.chip-item b{color:var(--ink);font-weight:600}

/* ---- print: ATS-safe, auto-fitted to two letter pages ----
   Every dimension below is expressed in rem off a single root size, so the whole
   document scales from one variable. tools/fit-resume.mjs binary-searches that
   variable for the largest type that still lands on two pages, which means a
   content edit no longer needs the scale re-tuned by hand.
   rem, not em, so nested elements do not compound. */
@media print{
  /* A4 at Word's Narrow preset. tools/fit-resume.mjs verifies the fitted scale
     paginates on BOTH A4 and Letter, since a US reader will print Letter. */
  @page{size:A4;margin:12.7mm}
  html{font-size:var(--cv-base,10.50pt)}
  header.site,footer.site,nav.site,.cv-actions,.no-print{display:none!important}
  html,body{background:#fff;color:#000;line-height:1.37}
  body{font-size:1rem}
  .wrap{max-width:none;margin:0;padding:0}
  a,.cv-contact a{color:#000;text-decoration:none}
  .cv-head{padding-bottom:.505rem;border-bottom:1pt solid #000}
  .cv-head h1{font-size:1.97rem;margin:0 0 .1rem;letter-spacing:-.02em}
  .cv-role{font-size:1.111rem;margin:0 0 .505rem;color:#000}
  .cv-contact{font-size:.909rem;gap:1.2rem;color:#000;font-family:var(--sans);
    justify-content:space-between;width:100%}
  .cv-block{padding:.687rem 0 0;margin-top:0;border-top:0.5pt solid #666}
  .cv-block > h2{font-size:.97rem;letter-spacing:.08em;margin:0 0 .505rem;color:#000;font-family:var(--sans)}
  /* Chrome largely ignores break-after on GRID ITEMS, so the org/dates row is a
     nested grid inside a plain block head. The head is then a normal block box,
     where break-inside/break-after are honoured, and it stays welded to its
     bullets. Entries of three bullets or fewer never split at all. */
  .cv-entry{display:block;padding:0 0 .525rem;break-inside:auto}
  .cv-entry-head{break-inside:avoid;break-after:avoid;page-break-after:avoid}
  .cv-entry-line{display:grid;grid-template-columns:1fr auto;column-gap:1.414rem;align-items:baseline}
  .cv-entry h3{font-size:1.081rem;margin:0}
  .cv-entry .cv-org{font-size:1rem;margin:0;color:#000}
  .cv-entry .cv-dates{text-align:right;white-space:nowrap;font-size:.98rem;
    margin:0 0 .202rem;color:#000;font-family:var(--sans);padding:0}
  .cv-entry--short{break-inside:avoid;page-break-inside:avoid}
  .cv-entry ul,.cv-summary{padding-left:1.313rem;margin:0}
  .cv-entry li,.cv-summary li{font-size:.97rem;line-height:1.38;margin:0 0 .172rem;max-width:none}
  .cv-entry li::marker,.cv-summary li::marker{color:#000}
  .cv-cert{padding-bottom:.283rem;break-inside:avoid}
  .cv-cert .cv-dates{margin:0}
  .cv-skillline{font-size:.97rem;line-height:1.4;margin:0 0 .222rem;max-width:none}
  .cv-skillline strong{font-size:.97rem;text-transform:none;letter-spacing:0;
    color:#000;font-family:var(--sans)}
  /* Bullets may flow across the break; a role heading must never be stranded. */
  .cv-entry ul,.cv-summary{orphans:2;widows:2}
  .cv-block{break-inside:auto}
  .cv-block > h2{break-after:avoid;page-break-after:avoid}
}

/* ---- analytical pipeline animation ----
   Marks and slices take their fill from a per-observation attribute, not from
   CSS, so the colour assigned in the table follows the datum into every view. */
.pipe{margin:26px 0 34px;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);
  padding:18px 0 14px}
/* The figure must not paint outside itself. overflow:visible let table rows
   and stray marks render over the page's own text, which is what made the
   figure look broken rather than merely mis-sized. The viewBox is fitted to
   the drawn content, so clipping removes nothing that should be seen. */
.pipe svg{display:block;width:100%;height:auto;overflow:hidden}
.pipe-caption{font-family:var(--mono);font-size:11.5px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);margin:8px 0 0;opacity:0;transition:opacity .32s ease;min-height:1.2em}
.pipe-caption.in{opacity:1}

.pl-axis{opacity:0;transition:opacity .45s ease}
.pl-axis.on{opacity:1}
.pl-axis line{stroke:var(--rule);stroke-width:1}
.pl-axis .pl-grid{stroke:var(--hair)}

.pl-marks{opacity:0;transition:opacity .4s ease}
.pl-marks.on{opacity:1}
.pl-mk{transform-box:view-box;transform-origin:0 0;
  transition:transform .62s cubic-bezier(.4,0,.2,1),opacity .45s ease}
.pl-mk rect{opacity:.86}
.pl-mk.gone{opacity:0}

.pl-layer{opacity:0;transition:opacity .4s ease;pointer-events:none}
.pl-layer.on{opacity:1}

.pl-t{font-family:var(--mono);font-size:10.5px;fill:var(--muted);letter-spacing:.04em}
.pl-num{fill:var(--ink);font-variant-numeric:tabular-nums}
.pl-th{fill:var(--muted);letter-spacing:.12em;text-transform:uppercase;font-size:9px}
.pl-trule,.pl-legrule{stroke:var(--ink);stroke-width:1}
.pl-tr{opacity:0;transform:translateX(-6px);
  transition:opacity .3s ease,transform .3s cubic-bezier(.4,0,.2,1)}
.pl-tr.on{opacity:1;transform:none}
/* the validation column only exists once the check has been run */
.pl-val{opacity:0;transition:opacity .35s ease}
.pl-layer.checked .pl-val{opacity:1}
.pl-vmark{font-size:12px;font-weight:600}
.pl-ok{fill:var(--s2)}
.pl-bad{fill:var(--s4)}
.pl-x{stroke:var(--s4);stroke-width:1.6;opacity:.9;stroke-linecap:round}

/* terminal */
.pl-term{font-family:var(--mono);font-size:15px;fill:var(--ink);letter-spacing:.02em}
.pl-term-p{fill:var(--signal)}
.pl-caret{fill:var(--signal);opacity:.85;animation:pl-blink 1s steps(1) infinite}
@keyframes pl-blink{50%{opacity:0}}

/* spread, horizontal */
.pl-box{fill:none;stroke:var(--ink);stroke-width:1.2;opacity:.55}
.pl-whisk{stroke:var(--muted);stroke-width:1}
.pl-med{stroke:var(--ink);stroke-width:2}
.pl-cen{font-size:10px}
.pl-mean{stroke:var(--s3);stroke-width:1.4}
.pl-med2{stroke:var(--s1);stroke-width:1.4}
.pl-mode{stroke:var(--s4);stroke-width:1.4}

.pl-slice{stroke:var(--paper);stroke-width:1.5;opacity:.88;
  transform-box:view-box;transition:transform .8s cubic-bezier(.4,0,.2,1)}
.pl-hole{fill:var(--paper)}

.pl-line{fill:none;transform-box:view-box;transition:transform .8s cubic-bezier(.4,0,.2,1);stroke:var(--muted);stroke-width:1.4;stroke-linejoin:round;
  stroke-dasharray:1400;stroke-dashoffset:1400}
.pl-layer.on .pl-line{animation:pl-draw 1s ease forwards}
@keyframes pl-draw{to{stroke-dashoffset:0}}

/* geospatial */
/* the sweep runs along the origin's own parallel and meridian, not screen axes */
.pl-cross{fill:none;stroke:var(--signal);stroke-width:1.2;opacity:.75;
  transition:opacity .55s ease}
/* Once the graticule is up the sweep lines sit between its parallels and read as
   stray marks, so they retire rather than linger. */
.pl-cross.spent{opacity:.14}
.pl-geogrid{opacity:0;transition:opacity .6s ease}
.pl-geogrid.on{opacity:1}
.pl-geogrid line,.pl-geogrid path{stroke:var(--hair);stroke-width:1;fill:none}
.pl-grat{fill:var(--muted);font-size:8.5px;letter-spacing:.08em}
/* coastline resolves out of blur once the graticule is established */
.pl-land{opacity:0;filter:blur(7px);transition:opacity .85s ease,filter .95s ease}
.pl-land.on{opacity:1;filter:blur(0)}
.pl-coast{fill:var(--signal);opacity:.09;stroke:var(--signal);stroke-width:1;stroke-opacity:.35}
.pl-lake{fill:var(--paper);stroke:var(--signal);stroke-width:.8;stroke-opacity:.3}
.pl-gp{opacity:0;transform:scale(.4);transform-box:fill-box;transform-origin:center;
  transition:opacity .3s ease,transform .3s cubic-bezier(.34,1.4,.64,1)}
.pl-gp.on{opacity:.9;transform:none}
.pl-geol{fill:var(--signal);font-size:10px;letter-spacing:.08em}

/* the read */
.pl-w{font-family:var(--sans);font-size:13.5px;fill:var(--body)}
.pl-scan{fill:var(--signal);opacity:.15;transform-box:view-box;transform-origin:0 0;
  transition:transform .08s linear,opacity .15s ease}

/* status row: NLP indicator and completion ring */
.pl-nlp{opacity:0;transition:opacity .3s ease}
.pl-nlp.on{opacity:1}
.pl-nlp-t{fill:var(--s2);text-anchor:end;letter-spacing:.14em;font-size:9px}
.pl-nlp-dot{fill:var(--s2)}
.pl-nlp-ring{fill:none;stroke:var(--s2);stroke-width:1;
  transform-box:fill-box;transform-origin:center}
.pl-nlp.on .pl-nlp-ring{animation:pl-pulse 1.5s ease-out infinite}
/* scale, not r: Safari cannot animate SVG geometry properties */
@keyframes pl-pulse{from{transform:scale(1);opacity:.75}to{transform:scale(3);opacity:0}}
.pl-prog-bg{fill:none;stroke:var(--rule);stroke-width:2}
.pl-prog{fill:none;stroke:var(--signal);stroke-width:2;stroke-linecap:round;
  transition:stroke-dashoffset .5s ease}

.pl-ring{fill:none;stroke:var(--lift);stroke-width:1.4}
.pl-lead{stroke:var(--lift);stroke-width:1}
.pl-note{fill:var(--lift);letter-spacing:.06em}

/* Narrow viewports keep the figure. Type is scaled in viewBox units, because a
   720-unit box on a 375px screen renders 10px type at roughly 5px. */
.pipe.narrow .pl-t{font-size:13px}
.pipe.narrow .pl-th{font-size:10px}
.pipe.narrow .pl-term{font-size:19px}
.pipe.narrow .pl-vmark{font-size:15px}
.pipe.narrow .pl-cen{font-size:12px}
.pipe.narrow .pl-grat{font-size:10px}
.pipe.narrow .pl-geol{font-size:11px}
.pipe.narrow .pl-caret{width:9px;height:18px}
.pipe.narrow .pl-gp{r:5}
.pipe-caption{font-size:11.5px}
@media (max-width:620px){.pipe-caption{font-size:10.5px;letter-spacing:.08em}}
@media (prefers-reduced-motion:reduce){
  .pl-mk,.pl-scan,.pipe-caption,.pl-tr,.pl-cross,.pl-geogrid,.pl-gp{transition:none}
  .pl-caret,.pl-nlp.on .pl-nlp-ring{animation:none}
  .pl-layer.on .pl-line{animation:none;stroke-dashoffset:0}
}

/* ---- conditions strip ----
   A grid, not a joined list: both readings share column tracks, so temperature
   sits under temperature and AQ under AQ. max-content sizes each track to the
   wider of the two rows, and every row emits all six cells - an omitted cell
   would shift everything after it out of true. */
.cond{border-bottom:1px solid var(--rule);padding:9px 0 10px;
  font-family:var(--mono);font-size:11px;letter-spacing:.05em;color:var(--muted);
  display:grid;grid-template-columns:repeat(6,max-content);gap:3px 22px;
  overflow-x:auto;scrollbar-width:none}
.cond::-webkit-scrollbar{display:none}
.cond-row{display:contents}
/* display:grid on .cond beats the UA [hidden] rule, so the strip kept its border
   and padding while empty. Restore the hide. */
.cond[hidden]{display:none}
.cond-c{line-height:1.8;white-space:nowrap}
.cond-k{color:var(--signal);font-weight:600}
.cond-n{font-variant-numeric:tabular-nums}
.cond-m{text-align:center}
@media (max-width:760px){
  .cond{font-size:10px;letter-spacing:.03em;gap:2px 14px}
}
/* On a phone the six columns overflow into a scroll nobody discovers. Sun R+S is
   by far the widest and the least consulted, so it is dropped rather than hidden
   behind a gesture. */
@media (max-width:620px){
  .cond{grid-template-columns:repeat(5,max-content);gap:2px 8px}
  .cond-row .cond-c:nth-child(4){display:none}
}
/* The umbrella only appears above 40% chance, so the widest state is rare and
   easy to miss by eye - it overflowed by 6px at 393px until the harness caught
   it. Sized for the wet case, not the dry one. */
@media (max-width:400px){
  .cond{gap:2px 6px;font-size:9.5px}
  .cond-i{font-size:1.05em}
}
@media print{.cond{display:none!important}}
.cond-i{font-size:1.15em;line-height:1;vertical-align:-.12em;
  font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}

/* ---- gem icons ---- */
.ico{width:13px;height:13px;display:inline-block;vertical-align:-1.5px;margin-right:7px}
.ico-gem{color:var(--signal)}
.ico-ruby{color:var(--alert)}
.ico-diamond{color:var(--muted)}

/* ---- theme toggle ---- */
button.tt{background:none;border:1px solid var(--rule);color:var(--muted);
  width:26px;height:26px;padding:0;cursor:pointer;display:inline-flex;
  align-items:center;justify-content:center;vertical-align:-7px;margin-left:4px}
button.tt:hover{border-color:var(--signal);color:var(--signal)}
button.tt svg{width:13px;height:13px;display:block}
.tt-sun{display:none}
.tt-moon{display:block}
:root[data-resolved="dark"] .tt-sun{display:block}
:root[data-resolved="dark"] .tt-moon{display:none}

/* ---- card layout on small screens ----
   A later .card rule was overriding the earlier mobile override, so a phone kept
   the 132px date rail and a 26px gutter - about 40% of a 393px screen spent on
   two words. The column now shrinks to its content and the date stacks. */
@media (max-width:760px){
  .card{grid-template-columns:auto 1fr;gap:0 14px;padding:22px 0}
  .card-meta{line-height:1.55}
  .card-meta .mo,.card-meta .yr{display:block}
  .card-meta .status-flag{display:inline-block;margin-top:4px}
}
@media (max-width:430px){
  .card{gap:0 11px}
  .card-meta{font-size:10.5px;letter-spacing:.05em}
}
.pl-obs{opacity:.9}
.pl-fence{fill:none;stroke:var(--alert);stroke-width:1.4}

/* ---- live flight ----
   Drawn only once a real flight has been resolved. The decorative route map
   keeps the card alive until then and steps aside rather than competing: two
   maps on one card is one map too many, and only one of them is data. */
.ca-hasLive .ca-routes,.ca-hasLive .ca-nodes{opacity:0;transition:opacity .5s ease}
.ca-hasLive .ca-plane{transform-box:view-box;transform-origin:0 0;opacity:1}
/* the readouts sit right of the card's text, so the mask can reach full
   strength sooner and let them be read */
.card-art svg.ca-hasLive{
  -webkit-mask-image:linear-gradient(to right,transparent 36%,#000 68%);
  mask-image:linear-gradient(to right,transparent 36%,#000 68%);
}
.ca-fl-rest{fill:none;stroke:currentColor;stroke-width:1.1;opacity:.42;
  stroke-dasharray:4 5;stroke-linecap:round}
.ca-fl-done{fill:none;stroke:currentColor;stroke-width:1.6;opacity:.95;stroke-linecap:round}
.ca-fl-end{fill:currentColor;opacity:.95}
.ca-fl-code{font-family:var(--mono);font-size:9px;letter-spacing:.09em;
  fill:currentColor;opacity:.9;font-weight:600}
.ca-fl-id{font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;
  fill:currentColor;opacity:1;font-weight:700}
.ca-fl-src{font-family:var(--mono);font-size:6.5px;letter-spacing:.16em;
  fill:currentColor;opacity:.55}
.ca-fl-n{font-family:var(--mono);font-size:8.5px;letter-spacing:.05em;
  fill:currentColor;opacity:.78}
.ca-fl-pct{font-family:var(--mono);font-size:8.5px;letter-spacing:.05em;
  fill:currentColor;opacity:1;font-weight:700}
/* an en-route field is named only while the aircraft is beside it */
.ca-fl-wp{opacity:0;transition:opacity .5s ease}
.ca-fl-wp.on{opacity:.62}
.ca-fl-wp circle{fill:currentColor}
.ca-fl-wp text{font-family:var(--mono);font-size:7.5px;letter-spacing:.08em;fill:currentColor}
@media (prefers-reduced-motion:reduce){
  .ca-fl-wp,.ca-hasLive .ca-routes,.ca-hasLive .ca-nodes{transition:none}
}
