/* Editorial business analysis portfolio. Scoped to preserve other local projects. */
.editorial {
 --bg-body:#17221f; --bg-surface:#22312b; --bg-surface-hover:#2b3d34;
 --text-primary:#f5f3eb; --text-secondary:#c3cdc6; --primary-color:#b5ddc1;
 --text-accent:var(--primary-color); --border:#4a6154; --border-hover:#b5ddc1;
 --primary-gradient:linear-gradient(135deg,#235d47,#174331);
 background:var(--bg-body); color:var(--text-primary);
}
html.light-theme .editorial { --bg-body:#f7f6f0; --bg-surface:#ffffff; --bg-surface-hover:#edf2ec; --text-primary:#202923; --text-secondary:#536157; --primary-color:#235d47; --border:#ccd6cc; --border-hover:#235d47; background:var(--bg-body); color:var(--text-primary); }
.editorial .container {width:calc(100% - 64px);max-width:1160px;}
.editorial main > section {padding:80px 0;scroll-margin-top:32px;}
.editorial h1,.editorial h2,.editorial h3 {color:var(--text-primary);letter-spacing:-.04em;}
.editorial p,.editorial dd {font-size:16px;line-height:1.75;}
.editorial .section__title {font-size:clamp(30px,3.4vw,44px);line-height:1.15;margin-bottom:24px;text-align:left;}
.editorial .section__title::after {display:none;}
.editorial .section__subtitle__text {max-width:720px;margin:0 0 36px;color:var(--text-secondary);text-align:left;}
.editorial .small-label {font:12px Inter,sans-serif;letter-spacing:.1em;color:var(--primary-color);margin-bottom:18px;}
.editorial .header {background:var(--bg-body);border-color:var(--border);}
.editorial .nav__list {gap:20px;}
.editorial .nav__link {font-size:12px;}
.editorial .hero {padding:145px 0 80px;overflow:visible;}
.editorial .hero__container {grid-template-columns:1.6fr 1fr;gap:56px;align-items:end;}
.editorial .hero__eyebrow {font:11px/1.7 Inter,sans-serif;letter-spacing:.08em;color:var(--primary-color);margin-bottom:24px;display:block;}
.editorial .hero-name {font-size:18px;color:var(--text-secondary);margin-bottom:18px;}
.editorial .hero__statement {font-size:clamp(38px,4.4vw,62px);line-height:1.08;font-weight:500;max-width:650px;}
.editorial .hero__description {font-size:18px;line-height:1.75;max-width:580px;margin:28px 0;}
.editorial .hero__actions {display:flex;gap:12px;flex-wrap:wrap;}
.editorial .hero-role {font-size:13px;margin-top:22px;color:var(--text-secondary);}
.editorial-portrait {margin:0;min-width:0;}
.editorial-portrait figcaption {font-size:12px;padding-top:14px;color:var(--text-secondary);border-top:1px solid var(--border);margin-top:16px;}
.editorial .btn {font-size:14px;min-height:48px;display:inline-flex;align-items:center;justify-content:center;gap:10px;letter-spacing:0;}
.editorial .btn--primary {background:var(--primary-color);color:var(--bg-body);border:1px solid var(--primary-color);box-shadow:none;}
.editorial .btn--primary:hover {background:var(--primary-color);filter:brightness(1.08);transform:translateY(-1px);}
.editorial .btn--outline {border:1px solid var(--border-hover);color:var(--primary-color);background:transparent;}
.editorial #data-passion,.editorial #about,.editorial #projects {border-top:1px solid var(--border);}
.case-grid {display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;}
.editorial .case-card {display:flex;flex-direction:column;border:1px solid var(--border);background:var(--bg-surface);border-radius:5px;overflow:hidden;min-width:0;}
.case-heading {padding:24px;min-height:138px;}
.case-number {font-size:12px;color:var(--primary-color);display:block;margin-bottom:14px;}
.case-heading h3 {font-size:23px;line-height:1.25;font-weight:500;}
.case-card > img {width:100%;height:auto;aspect-ratio:800/450;object-fit:contain;}
.editorial .case-caption {font-size:11px;line-height:1.5;padding:10px 24px;color:var(--text-secondary);margin:0;border-bottom:1px solid var(--border);}
.case-body {padding:24px;display:flex;flex-direction:column;flex:1;}
.editorial .case-question {font-size:17px;font-weight:500;line-height:1.5;margin:0 0 22px;}
.case-body dt {font-size:12px;font-weight:600;color:var(--primary-color);margin-bottom:6px;}
.editorial .case-body dd {font-size:15px;color:var(--text-secondary);margin:0 0 20px;line-height:1.65;}
.case-body .tool-tags {margin-top:auto;padding-top:8px;}
.editorial .project-card__cta {font-size:14px;color:var(--primary-color);padding-top:20px;}
.editorial .tool-tags {display:flex;gap:7px;flex-wrap:wrap;list-style:none;}
.editorial .tool-tags li {font-size:12px;color:var(--text-primary);border:1px solid var(--border);padding:5px 9px;background:var(--bg-body);}
.editorial .capabilities {display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;border:0;margin-top:36px;}
.editorial .capability {padding:28px;border:1px solid var(--border);background:var(--bg-surface);border-radius:5px;display:flex;flex-direction:column;min-width:0;}
.editorial .capability__number {color:var(--primary-color);padding:0;font-size:13px;margin-bottom:30px;}
.editorial .capability h3 {font-size:23px;line-height:1.3;padding:0;margin:0 0 18px;}
.editorial .capability p {font-size:16px;padding:0;margin:0 0 24px;color:var(--text-secondary);}
.editorial .capability .tool-tags {padding:0;margin-top:auto;}
.editorial .method-note {border-left:3px solid var(--primary-color);padding:8px 0 8px 24px;margin-top:36px;max-width:850px;color:var(--text-secondary);}
.editorial .about__content {display:grid;grid-template-columns:3fr 2fr;gap:64px;}
.editorial .about__text p {font-size:17px;line-height:1.8;}
.editorial .bio-statement {font-size:30px;}
.editorial .about__skills {grid-column:auto;display:flex;align-content:start;gap:10px;}
.editorial .about__skills .skill-bar {flex-basis:100%;background:var(--bg-surface);padding:14px;}
.editorial .about__skills .skill-bar__name {font-size:14px;}
.editorial .bio-links a {font-size:14px;min-height:44px;}
.editorial .timeline__content {background:var(--bg-surface);padding:24px 28px;}
.editorial .timeline__content::after {display:none;}
.editorial .timeline__description {font-size:16px;}
.editorial .experience-intro {font-size:16px;}
.editorial .projects-grid {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px;}
.editorial .project-card__visual {height:180px;}
.editorial .project-card__body {padding:24px;}
.editorial .project-card__desc {font-size:16px;line-height:1.7;}
.editorial .project-card__title {font-size:24px;}
.editorial .project-card {box-shadow:none;transition:border-color .2s,transform .2s;}
.editorial .project-card:hover {transform:translateY(-3px);border-color:var(--border-hover);box-shadow:none;}
.editorial .more-projects {margin-top:28px;}
.editorial .more-projects summary {padding:16px 0;min-height:48px;color:var(--primary-color);cursor:pointer;font-size:16px;}
.editorial .more-projects .projects-grid {margin-top:24px;}
/* No form any more: a half-empty second column would read as breakage. */
.editorial .contact__container {grid-template-columns:minmax(0,1fr) auto;gap:56px;align-items:start;}
.editorial .contact__info {max-width:660px;}
.editorial .contact__link {font-size:15px;overflow-wrap:anywhere;}
.editorial .contact__form {background:var(--bg-surface);border:1px solid var(--border);padding:28px;border-radius:5px;}
.editorial input,.editorial textarea {background:var(--bg-body);color:var(--text-primary);border-color:var(--border);font-size:16px;}
.editorial input::placeholder,.editorial textarea::placeholder {color:var(--text-secondary);opacity:1;}
.editorial .footer {background:var(--bg-body);border-top:1px solid var(--border);}
.editorial .modal-content {background:var(--bg-surface);color:var(--text-primary);border-color:var(--border);}
.editorial .modal-description,.editorial .modal-description p,.editorial .modal-description li {color:var(--text-secondary);font-size:16px;}
.editorial .modal-description h4,.editorial .modal-title,.editorial .modal-close {color:var(--text-primary);}
.editorial .modal-tag {color:var(--text-primary);background:var(--bg-body);border-color:var(--border);}
.editorial .modal-image {filter:none;max-height:280px;object-fit:contain;background:#18241f;}
.editorial .modal-close {min-width:44px;min-height:44px;}
.editorial :focus-visible {outline:3px solid var(--primary-color);outline-offset:5px;}
.editorial .pointer-halo,.editorial .pointer-comet,.editorial .custom-cursor,.editorial .cursor-dot,.editorial .cursor-outline {display:none!important;}
@media(max-width:1000px) { .editorial .hero__container {gap:28px;} .editorial .nav__list {gap:12px;} .case-grid {gap:16px;} .case-heading,.case-body {padding:18px;} .editorial .capabilities {gap:16px;} .editorial .capability {padding:20px;} }
@media(max-width:768px) {
 .editorial .container {width:calc(100% - 40px);}
 .editorial main > section {padding:48px 0;}
 .editorial .hero {padding:112px 0 48px;}
 .editorial .hero__container,.editorial .about__content {grid-template-columns:1fr;gap:32px;}
 .editorial .hero__statement {font-size:clamp(36px,7.5vw,52px);}
 .editorial .hero__description {font-size:17px;}
 .editorial-portrait {max-width:440px;width:100%;}
  .case-grid,.editorial .capabilities,.editorial .projects-grid {grid-template-columns:1fr;gap:20px;}
 .case-heading {min-height:0;padding:24px;}
 .case-body {padding:24px;}
 .editorial .nav.active {background:var(--bg-body);}
 .editorial .timeline__content {padding:22px;}
 .editorial .contact__container {grid-template-columns:1fr;gap:28px;}
 .editorial .contact__form {padding:22px;}
 .editorial .about__skills {grid-column:auto;}
}
@media(prefers-reduced-motion:reduce) {html {scroll-behavior:auto;} .editorial *,.editorial *::before,.editorial *::after {animation:none!important;transition:none!important;} }
/* CV shares the editorial palette and remains printable. */
.cv-editorial .cv-main {background:var(--bg-surface);border:1px solid var(--border);box-shadow:none;border-radius:5px;color:var(--text-primary);}
.cv-editorial h1,.cv-editorial h2,.cv-editorial h3,.cv-editorial h4 {font-family:Inter,sans-serif;color:var(--text-primary);}
.cv-editorial .cv-intro h2,.cv-editorial .job-company {color:var(--primary-color);}
.cv-editorial p,.cv-editorial li,.cv-editorial .cv-contact-item,.cv-editorial .job-date {color:var(--text-secondary);}
.cv-editorial .skill-pill {background:var(--bg-body);color:var(--text-primary);border-color:var(--border);}
.cv-editorial .cv-header,.cv-editorial .cv-section {border-color:var(--border);}
.cv-editorial .back-link a {color:var(--primary-color)!important;}
.cv-editorial .cv-section h3::after {background:var(--primary-color);}
@media(max-width:768px) {.cv-editorial .cv-main {margin:80px 16px 24px;padding:24px;} .cv-editorial .download-btn-container {position:static;margin-bottom:24px;} .cv-editorial .cv-header {display:flex;flex-direction:column;} }
@media print {.cv-editorial {background:white;color:#202923;} .cv-editorial .cv-main {border:0;margin:0;padding:0;background:white;color:#202923;} .cv-editorial p,.cv-editorial li,.cv-editorial h1,.cv-editorial h2,.cv-editorial h3,.cv-editorial h4,.cv-editorial .job-date,.cv-editorial .job-company,.cv-editorial .cv-contact-item {color:#202923;} .cv-editorial .skill-pill {color:#202923;background:white;border-color:#999;} .cv-editorial .back-link,.cv-editorial .download-btn-container {display:none;} }

.editorial .modal-backdrop {visibility:hidden;}
.editorial .modal-backdrop.active {visibility:visible;}
.editorial .modal-tools-title {color:var(--text-primary);}
.editorial .modal-close {background:var(--bg-surface);border:1px solid var(--border);border-radius:50%;z-index:2;line-height:1;}
/* 27. Hero staggered entry, stats band and section reveal. */
.editorial .hero__eyebrow   {animation:heroEntry .7s ease both .05s;}
.editorial .hero-name       {animation:heroEntry .7s ease both .15s;}
.editorial .hero__statement {animation:heroEntry .7s ease both .25s;}
.editorial .hero__description {animation:heroEntry .7s ease both .40s;}
.editorial .hero__actions   {animation:heroEntry .7s ease both .50s;}
.editorial .editorial-portrait {animation:heroEntry .8s ease both .30s;}
html.light-theme .editorial .hero__description {color:var(--text-secondary);}
@media (min-width:600px) and (max-width:768px) {.editorial .case-card > img {max-height:260px;} }
.cv-editorial main > .cv-section {padding:0;margin:32px 0;}
.cv-editorial .cv-role-title {color:var(--text-primary);}
.cv-editorial .cv-company,.cv-editorial .cv-desc li::before {color:var(--primary-color);}
.cv-editorial .cv-date {color:var(--text-secondary);}
.cv-editorial .cv-avatar {border-color:var(--primary-color);box-shadow:none;}
.cv-editorial .cv-avatar-container {background:none;box-shadow:none;}
@media(max-width:640px) {.cv-editorial .cv-role-header {flex-direction:column;gap:6px;} .cv-editorial .cv-intro h1 {font-size:30px;} }
@media print {.cv-editorial .cv-role-title,.cv-editorial .cv-date,.cv-editorial .cv-company {color:#202923;} .cv-editorial .cv-item {break-inside:avoid;} }

/* A hidden dialog is absent from layout and keyboard navigation. */
.editorial .modal-backdrop {display:none;transition:none;}
.editorial .modal-backdrop.active {display:flex;}
.editorial .modal-content {transition:none;transform:none;}

/* Locally extracted portrait with real transparency and natural hair edges. */
.editorial-portrait {max-width:320px;margin-left:auto;}
.editorial-portrait img {
    width:100%;height:auto;max-height:400px;aspect-ratio:1059/1320;
    object-fit:contain;border-radius:0;background:transparent;display:block;
}
.editorial-portrait figcaption {border-top:0;margin-top:8px;text-align:center;}
@media(max-width:768px) {.editorial-portrait {max-width:280px;margin:0 auto;} .editorial-portrait img {max-height:340px;} }

/* Interactive data field. Decorative layers never intercept page controls. */
.editorial {isolation:isolate;}
/* Anchored to the first screen rather than the viewport (TASK-012). As a fixed
   layer it was a wallpaper: identical in every viewport, so scrolling never changed
   it, and after TASK-011 made the lower bands opaque it kept repainting behind them
   for the whole visit. Absolute means it scrolls away like anything else, and the
   observer in portfolio.js stops the canvas once it has. */
.data-atmosphere {position:absolute;top:0;left:0;width:100%;height:100vh;z-index:-1;pointer-events:none;overflow:hidden;--field-x:72%;--field-y:35%;}
.data-atmosphere::before {content:"";position:absolute;inset:0;background:radial-gradient(ellipse at var(--field-x) var(--field-y),rgba(110,171,139,.17),transparent 52%),radial-gradient(ellipse at 8% 78%,rgba(119,154,164,.09),transparent 50%);}
/* 0.65px dots at .10 opacity were invisible. Now visible, masked off the left where
   the headline sits and faded out at the foot so the grid resolves into the band
   below instead of being sliced off by it. */
.data-atmosphere::after {content:"";position:absolute;inset:0;background-image:radial-gradient(var(--primary-color) 1.15px,transparent 1.45px);background-size:32px 32px;opacity:.18;
  -webkit-mask-image:linear-gradient(90deg,transparent 6%,#000 60%),linear-gradient(180deg,#000 55%,transparent 92%);
  mask-image:linear-gradient(90deg,transparent 6%,#000 60%),linear-gradient(180deg,#000 55%,transparent 92%);
  -webkit-mask-composite:source-in;mask-composite:intersect;}
.data-atmosphere canvas {width:100%;height:100%;display:block;}
.editorial .mouse-aura {position:fixed;top:0;left:0;width:26px;height:26px;border:1px solid var(--primary-color);border-radius:50%;pointer-events:none;z-index:2100;opacity:0;transition:width .18s,height .18s,opacity .18s,background .18s;margin:-13px 0 0 -13px;}
.editorial .mouse-aura.is-visible {opacity:.55;}
.editorial .mouse-aura.is-interactive {width:40px;height:40px;margin:-20px 0 0 -20px;background:rgba(112,171,139,.10);}
.editorial .effects-toggle {display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:10px 14px;border:1px solid var(--border);border-radius:4px;background:var(--bg-body);color:var(--text-secondary);font:12px Inter,sans-serif;cursor:pointer;}
.effects-toggle::before {content:"";width:6px;height:6px;border-radius:50%;background:var(--primary-color);}
.effects-toggle[aria-pressed="true"]::before {background:var(--text-secondary);}
.editorial .effects-toggle:disabled {cursor:default;opacity:.7;}
@media(hover:none),(pointer:coarse) {.editorial .mouse-aura {display:none;}}
@media(prefers-reduced-motion:reduce) {.editorial .mouse-aura {display:none;} .data-atmosphere::before {background:radial-gradient(ellipse at 72% 35%,rgba(110,171,139,.07),transparent 48%);}}

@media(max-width:768px) {.editorial .mouse-aura {display:none;} }

/* Stats band: flat theme colour, since --primary-gradient is unreadable on the dark surface. */
.editorial .stats {border-bottom:0;}
.editorial .stat-item {border-radius:5px;background:var(--bg-surface);border:1px solid var(--border);backdrop-filter:none;padding:26px 18px;transition:border-color .2s,transform .2s;}
.editorial .stat-item:hover {border-color:var(--border-hover);transform:translateY(-3px);}
/* Beats `html.light-theme .stat-number` (0,2,1), which still carries the old blue/purple gradient. */
html .editorial .stat-number,html .editorial .stat-suffix {background:none;background-clip:border-box;-webkit-background-clip:border-box;-webkit-text-fill-color:currentColor;color:var(--primary-color);font-family:Inter,sans-serif;letter-spacing:-.04em;}
.editorial .stat-number {font-size:clamp(34px,3.6vw,46px);}
.editorial .stat-suffix {font-size:clamp(22px,2.2vw,28px);}
.editorial .stat-label {font-size:12px;letter-spacing:.08em;color:var(--text-secondary);margin:0;}
@media(max-width:860px) {.editorial .stats__grid {grid-template-columns:repeat(2,1fr);gap:16px;}}

/* Sections carrying reveal-stagger cascade their cards instead of fading as one block. */
.editorial .reveal-stagger {opacity:1;transform:none;}
.editorial .reveal-stagger .case-card,
.editorial .reveal-stagger .capability,
.editorial .reveal-stagger .stat-item,
.editorial .reveal-stagger > .container > .projects-grid > .project-card {opacity:0;transform:translateY(20px);transition:opacity .6s ease,transform .6s cubic-bezier(.25,1,.5,1);}
.editorial .reveal-stagger.is-visible .case-card,
.editorial .reveal-stagger.is-visible .capability,
.editorial .reveal-stagger.is-visible .stat-item,
.editorial .reveal-stagger.is-visible > .container > .projects-grid > .project-card {opacity:1;transform:none;}
.editorial .reveal-stagger.is-visible .case-grid > :nth-child(2),
.editorial .reveal-stagger.is-visible .capabilities > :nth-child(2),
.editorial .reveal-stagger.is-visible .stats__grid > :nth-child(2),
.editorial .reveal-stagger.is-visible .projects-grid > :nth-child(2) {transition-delay:.10s;}
.editorial .reveal-stagger.is-visible .case-grid > :nth-child(3),
.editorial .reveal-stagger.is-visible .capabilities > :nth-child(3),
.editorial .reveal-stagger.is-visible .stats__grid > :nth-child(3) {transition-delay:.20s;}
.editorial .reveal-stagger.is-visible .stats__grid > :nth-child(4) {transition-delay:.30s;}

/* Cards read as interactive: the flat 3px lift was hard to notice. */
.editorial .case-card {transition:border-color .25s,transform .25s,box-shadow .25s;}
.editorial .case-card:hover,.editorial .case-card:focus-visible {transform:translateY(-4px);border-color:var(--border-hover);}
.editorial .project-card:hover {transform:translateY(-4px);}

/* Reduced motion: nothing may stay stuck at opacity 0 once the transitions are stripped. */
@media(prefers-reduced-motion:reduce) {
 .editorial .reveal-stagger .case-card,
 .editorial .reveal-stagger .capability,
 .editorial .reveal-stagger .stat-item,
 .editorial .reveal-stagger > .container > .projects-grid > .project-card {opacity:1;transform:none;}
 .editorial .reveal-section {opacity:1;transform:none;}
}

/* Case diagrams ship their own dark panel, so the contain-fit letterbox matches it. */
.editorial .case-card > img {background:#18241f;}

/* Capability bars: a full accent rule, no tier label. Flat --primary-color, because
   --primary-gradient is dark green and would disappear on the dark surface. */
.editorial .skill-bar__track {height:6px;margin-top:11px;background:var(--border);border-radius:100px;overflow:hidden;}
.editorial .skill-bar__fill {background:var(--primary-color);}

/* 28. Timeline: the analytical work of each role, beside the operational description. */
.editorial .timeline__date {grid-row:1 / -1;}
.editorial .timeline__data {grid-column:2;margin:0;padding:2px 48px 0 16px;border-left:2px solid var(--primary-color);font-size:15px;line-height:1.75;color:var(--text-secondary);}
.editorial .timeline__data-label {display:block;font:600 11px Inter,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:var(--primary-color);margin-bottom:6px;}
.editorial .timeline__content .tool-tags {grid-column:2;margin:0;}
@media(max-width:768px) {
 .editorial .timeline__data,.editorial .timeline__content .tool-tags {grid-column:auto;}
 .editorial .timeline__data {padding-right:0;}
}

/* 29. A UI capture is anchored top-left: cropping the centre loses the KPI row. */
.editorial .project-cover--ui {object-fit:cover;object-position:top left;background:#0f172a;}
.editorial .modal-image {max-height:420px;}

/* 30. Portrait caption on two levels: the post, then the employer. */
.editorial-portrait figcaption .portrait-role,
.editorial-portrait figcaption .portrait-org {display:block;}
.editorial-portrait figcaption .portrait-role {color:var(--text-primary);font-weight:500;}
.editorial-portrait figcaption .portrait-org {margin-top:2px;opacity:.8;}
/* The CV headline carries the target role; the line under it carries the real post. */
.cv-editorial .cv-current-post,
.cv-current-post {display:block;margin-top:4px;font-size:13px;font-weight:400;color:var(--text-secondary,#536157);letter-spacing:0;}

/* 31. The About thesis, marked like the method note so it reads as the argument. */
.editorial .bio-synthesis {border-left:3px solid var(--primary-color);padding:2px 0 2px 20px;margin:26px 0 0;font-size:17px;line-height:1.75;color:var(--text-primary);}
@media(max-width:768px) {.editorial .bio-synthesis {padding-left:16px;font-size:16px;}}

/* 32. vCard QR. A phone camera reads BEGIN:VCARD and offers to save the contact.
   The light background is fixed: inverted modules under the dark theme do not scan. */
.editorial .contact__qr {margin:0;text-align:center;justify-self:end;}
.editorial .contact__qr img {display:block;width:190px;height:190px;background:#fff;padding:10px;border-radius:6px;border:1px solid var(--border);}
.editorial .contact__qr figcaption {margin-top:12px;max-width:210px;font-size:12px;line-height:1.5;color:var(--text-secondary);}
@media(max-width:860px) {
 .editorial .contact__container {grid-template-columns:1fr;gap:32px;}
 .editorial .contact__qr {justify-self:start;text-align:left;}
 .editorial .contact__qr figcaption {max-width:190px;}
}

/* 33. LinkedIn brand mark. The contact links are inline, so the glyph rides the
   text baseline rather than being flex-centred. */
.editorial .contact__link svg.icon--brand {width:1.05em;height:1.05em;fill:#0A66C2;vertical-align:-.16em;}

/* 34. Chat panel. Fixed to the viewport corner, so it sits outside the editorial
   grid entirely and never has to negotiate with the section layout. Colours come
   from the theme variables, which means it follows the light/dark toggle for free. */
.chat {position:fixed;right:24px;bottom:24px;z-index:900;font-family:var(--font-body);}

.chat__launcher {display:inline-flex;align-items:center;gap:10px;padding:13px 20px;border:none;
  border-radius:999px;background:var(--primary-color);color:var(--bg-body);font-size:.9rem;font-weight:600;
  cursor:pointer;box-shadow:0 10px 30px rgba(0,0,0,.28);transition:transform .2s ease,box-shadow .2s ease;}
.chat__launcher:hover {transform:translateY(-2px);box-shadow:0 14px 36px rgba(0,0,0,.34);}
.chat__launcher svg {width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;flex-shrink:0;}
.chat.is-open .chat__launcher {display:none;}

.chat__panel {display:flex;flex-direction:column;width:min(400px,calc(100vw - 32px));height:min(620px,calc(100vh - 48px));
  background:var(--bg-body);border:1px solid var(--border);border-radius:16px;overflow:hidden;
  box-shadow:0 24px 60px rgba(0,0,0,.4);}
.chat__panel[hidden] {display:none;}

.chat__head {display:flex;align-items:flex-start;gap:8px;padding:16px 16px 14px;border-bottom:1px solid var(--border);}
.chat__head > div {flex:1;min-width:0;}
.chat__title {margin:0;font-size:.95rem;font-weight:700;color:var(--text-primary);}
.chat__note {margin:3px 0 0;font-size:.72rem;color:var(--text-secondary);letter-spacing:.01em;}
.chat__icon-btn {flex-shrink:0;width:30px;height:30px;padding:0;border:1px solid var(--border);
  border-radius:8px;background:transparent;color:var(--text-secondary);font-size:1.05rem;line-height:1;
  cursor:pointer;transition:color .18s ease,border-color .18s ease;}
.chat__icon-btn:hover {color:var(--primary-color);border-color:var(--primary-color);}

.chat__log {flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;}
.chat__msg {max-width:85%;padding:10px 13px;border-radius:13px;font-size:.87rem;line-height:1.55;
  white-space:pre-wrap;overflow-wrap:anywhere;}
.chat__msg--user {align-self:flex-end;background:var(--primary-color);color:var(--bg-body);border-bottom-right-radius:4px;}
.chat__msg--bot {align-self:flex-start;background:var(--bg-surface);border:1px solid var(--border);
  color:var(--text-primary);border-bottom-left-radius:4px;}
.chat__msg--bot.is-streaming {color:var(--text-secondary);}

.chat__starters {display:flex;flex-wrap:wrap;gap:7px;padding:0 16px 12px;}
.chat__starters:empty {display:none;}
.chat__starter {padding:7px 12px;border:1px solid var(--border);border-radius:999px;background:transparent;
  color:var(--text-secondary);font-family:inherit;font-size:.76rem;text-align:left;cursor:pointer;
  transition:color .18s ease,border-color .18s ease;}
.chat__starter:hover {color:var(--primary-color);border-color:var(--primary-color);}

.chat__form {display:flex;gap:8px;padding:12px 16px 16px;border-top:1px solid var(--border);align-items:flex-end;}
.chat__input {flex:1;resize:none;padding:10px 12px;border:1px solid var(--border);border-radius:10px;
  background:var(--bg-surface);color:var(--text-primary);font-family:inherit;font-size:.87rem;line-height:1.5;
  max-height:120px;overflow-y:auto;}
.chat__input:focus {outline:none;border-color:var(--primary-color);}
.chat__input:disabled {opacity:.55;}
.chat__send {flex-shrink:0;padding:10px 16px;border:none;border-radius:10px;background:var(--primary-color);
  color:var(--bg-body);font-family:inherit;font-size:.82rem;font-weight:600;cursor:pointer;}
.chat__send:disabled {opacity:.5;cursor:default;}

/* The launcher label costs too much width on a phone; the icon carries it alone. */
@media (max-width:560px) {
  .chat {right:16px;bottom:16px;}
  .chat__launcher-text {display:none;}
  .chat__launcher {padding:14px;}
  .chat__panel {width:calc(100vw - 32px);height:min(560px,calc(100vh - 100px));}
}

@media (prefers-reduced-motion:reduce) {
  .chat__launcher {transition:none;}
  .chat__launcher:hover {transform:none;}
}

/* 35. Source-code links on the project cards. The card itself is role="button"
   and opens the modal, so the link sits above it with its own click handling;
   see the guard in portfolio.js. Without that, clicking Code would also open
   the modal behind the new tab. */
.editorial .project-card__code {position:absolute;top:12px;right:12px;z-index:3;
  display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;
  background:rgba(8,12,22,.72);color:#fff;font-size:.74rem;font-weight:600;
  text-decoration:none;backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,.14);
  opacity:0;transform:translateY(-4px);transition:opacity .18s ease,transform .18s ease;}
.editorial .project-card {position:relative;}
.editorial .project-card:hover .project-card__code,
.editorial .project-card__code:focus-visible {opacity:1;transform:none;}
.editorial .project-card__code:hover {background:var(--primary-color);border-color:var(--primary-color);}
.editorial .project-card__code svg {width:13px;height:13px;fill:currentColor;}

/* Touch has no hover, so the link would never appear. Show it permanently. */
@media (hover:none) {
  .editorial .project-card__code {opacity:1;transform:none;}
}

/* 36. GitHub mark in the contact list, matching the LinkedIn one. */
.editorial .contact__link svg.icon--gh {fill:var(--text-primary);}

/* 37. The planner cover is a chart, not a UI capture, so it must never be
   cropped: losing the title or the baseline makes it unreadable. Contained
   inside the card's crop box, with the card surface showing through. */
.editorial .project-cover--chart {object-fit:contain;object-position:center;background:#f2f4ef;}

/* 38. Tonal score and elevation.
   The page ran 8,200px of scroll on two near-identical whites (#f7f6f0 body,
   #ffffff surface): the alternation existed but sat below the threshold where an
   eye reads it, so the whole site looked like one flat sheet. It now moves through
   four tones and one dark chapter.

       hero        paper    the fixed .data-atmosphere field shows through here
       stats       paper    and here, and nowhere else — the bands below are opaque
       #01 cases   sand
       #02 capab.  paper
       about       white    (already was)
       projects    DARK     the chapter break
       experience  paper
       contact     sand

   The bands are token overrides, not colour overrides. Everything inside already
   draws from var(--bg-*) / var(--text-*), and custom properties inherit down the
   tree whatever selector consumes them, so redefining the tokens on a section
   restyles its whole subtree without touching a single component rule. */
.editorial {
    --band-sand: #1c2925;
    --shadow-1: 0 1px 2px rgba(0,0,0,.28), 0 4px 14px rgba(0,0,0,.20);
    --shadow-2: 0 2px 6px rgba(0,0,0,.32), 0 18px 40px rgba(0,0,0,.32);
}
html.light-theme .editorial {
    --band-sand: #eceadf;
    --shadow-1: 0 1px 2px rgba(32,41,35,.04), 0 4px 12px rgba(32,41,35,.05);
    --shadow-2: 0 2px 4px rgba(32,41,35,.05), 0 14px 32px rgba(32,41,35,.10);
}

/* Opaque bands. Making these opaque is also what confines the fixed atmosphere
   field to the hero, which is the only place it was ever masked to be visible. */
.editorial #data-passion,
.editorial #contact { background: var(--band-sand); }
.editorial #toolbox,
.editorial #experience { background: var(--bg-body); }

/* The dark chapter. Projects carries the loudest imagery on the page — the AI
   illustrations — and a dark ground is what stops them reading as garish rather
   than deliberate. It is also the longest section, so the band is long enough to
   read as a chapter instead of a stripe. In the dark theme the same section has to
   go the other way: lighter than its surroundings, not darker. */
.editorial #projects { background: #1e2d28; border-top: 0; }
html.light-theme .editorial #projects {
    --bg-body: #17221f;
    --bg-surface: #22312b;
    --bg-surface-hover: #2b3d34;
    --text-primary: #f5f3eb;
    --text-secondary: #c3cdc6;
    --primary-color: #b5ddc1;
    --text-accent: var(--primary-color);
    --border: #3f564a;
    --border-hover: #b5ddc1;
    --shadow-1: 0 1px 2px rgba(0,0,0,.28), 0 4px 14px rgba(0,0,0,.20);
    --shadow-2: 0 2px 6px rgba(0,0,0,.32), 0 18px 40px rgba(0,0,0,.32);
    background: var(--bg-body);
    color: var(--text-primary);
    border-top: 0;
}
/* Three tag colours are hardcoded for the light theme back in styles.css and would
   survive the token swap as blue and amber on forest green. */
html.light-theme .editorial #projects .project-tag {
    background: var(--bg-body);
    border-color: var(--border);
    color: var(--text-secondary);
}

/* Elevation. Every card was a 1px border over a fill three percent away from the
   surface behind it, so nothing sat above anything. box-shadow was not in the
   existing transition list, so it has to be re-declared or the hover snaps. */
.editorial .project-card {
    box-shadow: var(--shadow-1);
    transition: border-color .2s, transform .2s, box-shadow .2s;
}
.editorial .project-card:hover { box-shadow: var(--shadow-2); }
.editorial .stat-item {
    box-shadow: var(--shadow-1);
    transition: border-color .2s, transform .2s, box-shadow .2s;
}
.editorial .stat-item:hover { box-shadow: var(--shadow-2); }
/* Capabilities and timeline entries are not clickable, so they get depth but no
   hover lift: a card that rises under the cursor and does nothing is a lie. */
.editorial .capability,
.editorial .timeline__content { box-shadow: var(--shadow-1); }

/* 39. Covers that stop shouting.

   This section also carried move D, a display serif on the three lines that make the
   argument. It was reverted on the owner's call after seeing it against the page:
   Instrument Serif is a display face with very high stroke contrast and Inter is a
   grotesque with almost none, so the two took turns rather than talking to each
   other. The page keeps its typographic contrast where it had it, in size and
   weight. Anything that reintroduces a second family has to be judged against Inter
   body copy, not on its own.

   E. The AI illustrations are the most saturated thing on the page and the least
   relevant work on it. The dark band already stopped them reading as garish; holding
   them back at rest and releasing them on hover puts the emphasis where the visitor
   put their attention instead of where the colour is.

   The planner cover is excluded: it is a chart, not an illustration, and its palette
   is the page's own. */
.editorial .project-cover:not(.project-cover--chart) { filter: saturate(.72) contrast(1.03); }
.editorial .project-card:hover .project-cover:not(.project-cover--chart),
.editorial .project-card:focus-within .project-cover:not(.project-cover--chart) { filter: none; }
.editorial .project-cover { transition: transform .55s cubic-bezier(.2,.7,.2,1), filter .45s ease; }

/* 40. The code veil: SQL and Python under the page, revealed around the cursor.

   The layer sits BETWEEN each band's colour and its text. Section content is lifted
   to z-index 2 below, the veil is 1, and a section's own background paints beneath
   both -- so the code can appear anywhere on the page and still cannot cover a word.
   That constraint is the whole design: this page argues that its author is careful,
   and an effect that makes it harder to read would refute the argument.

   One fixed layer has to cross bands of opposite lightness -- paper in most of the
   page, near-black in the projects chapter -- and no single ink reads on both.
   mix-blend-mode was the obvious answer and it failed: overlay against a near-white
   ground washes out to nothing. So the ink is a variable instead, and the script
   sets it from the --primary-color of whichever band the cursor is actually over.
   Only that band matters, because the reveal is a 190px circle around the cursor. */
.code-veil {
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    overflow: hidden;
    opacity: 0;
    transition: opacity .3s ease;
    --vx: -9999px;
    --vy: -9999px;
    -webkit-mask-image: radial-gradient(circle 190px at var(--vx) var(--vy), #000 0%, rgba(0,0,0,.72) 42%, transparent 100%);
    mask-image: radial-gradient(circle 190px at var(--vx) var(--vy), #000 0%, rgba(0,0,0,.72) 42%, transparent 100%);
}
.code-veil.is-lit { opacity: 1; }
.code-veil__grid {
    position: absolute;
    inset: -30px -20px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 30px 44px;
    align-content: start;
    color: var(--veil-ink, #235d47);
    opacity: .55;
}
.code-veil pre {
    margin: 0;
    font: 11.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    white-space: pre;
    overflow: hidden;
}

/* The lift that makes the veil safe. Scoped to section and footer containers, so the
   header keeps its own stacking. */
.editorial main > section > .container,
.editorial .footer__container { position: relative; z-index: 2; }

/* No cursor, no veil. Same conditions as the rest of the pointer effects. */
@media (hover: none), (pointer: coarse) { .code-veil { display: none; } }
@media (max-width: 768px) { .code-veil { display: none; } }
@media (prefers-reduced-motion: reduce) { .code-veil { display: none; } }

/* 41. X-ray on the portrait.

   The photo is the one opaque thing large enough to hide the veil completely, so
   hovering it opens a soft hole and the code shows through. Behind the portrait
   there is no band colour -- the hero is transparent -- so what comes through is the
   veil, the atmosphere field and the paper, in that order. "The data behind the
   person" is the page's actual argument, which is why this is the one place the
   effect earns a second appearance.

   The hole is 62% transparent at its centre and fully opaque by 150px, so it reads
   as translucency rather than a bite taken out of a face. The caption is untouched:
   the role and the employer stay fully legible whatever the cursor is doing. */
.editorial-portrait img { transition: filter .35s ease; }
.editorial-portrait.is-xray img {
    filter: grayscale(.32) contrast(1.03);
    -webkit-mask-image: radial-gradient(circle 150px at var(--px, 50%) var(--py, 50%),
        rgba(0,0,0,.38) 0%, rgba(0,0,0,.74) 46%, #000 100%);
    mask-image: radial-gradient(circle 150px at var(--px, 50%) var(--py, 50%),
        rgba(0,0,0,.38) 0%, rgba(0,0,0,.74) 46%, #000 100%);
}
@media (hover: none), (pointer: coarse) { .editorial-portrait.is-xray img { filter: none; -webkit-mask-image: none; mask-image: none; } }
@media (prefers-reduced-motion: reduce) { .editorial-portrait img { transition: none; } }

/* 42. The method note, restated as a query.

   The prose above it stays: it is the version a non-technical recruiter reads, and
   this page is read by both. The query is the same claim written formally, which is
   what the note itself describes doing.

   No syntax colouring. The palette is one accent by decision (ADR-0002) and a query
   lit up in four hues would be the third, fourth and fifth colours arriving through
   the back door. Comments carry the meaning here, so comments are the only thing
   set apart. */
.editorial :is(.method-query, .bio-query) {
    margin: 26px 0 0;
    max-width: 850px;
    border: 1px solid var(--border);
    border-radius: 5px;
    background: var(--bg-surface);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}
.editorial :is(.method-query, .bio-query) pre {
    margin: 0;
    padding: 22px 24px;
    overflow-x: auto;           /* long lines scroll here, never the page */
    font: 13px/1.75 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: var(--text-primary);
    tab-size: 4;
}
.editorial :is(.method-query, .bio-query) code { font: inherit; background: none; padding: 0; }
.editorial .method-query .q-c { color: var(--text-secondary); }
.editorial .method-query figcaption {
    padding: 11px 24px;
    border-top: 1px solid var(--border);
    font-size: 11px;
    letter-spacing: .04em;
    color: var(--text-secondary);
    background: var(--bg-body);
}
@media (max-width: 768px) {
    .editorial :is(.method-query, .bio-query) pre { padding: 18px; font-size: 12px; }
    .editorial .method-query figcaption { padding: 10px 18px; }
}

/* 43. The self-portrait query, in About.

   Same treatment as the method query in section 42 -- one code-block look on the
   page, not two -- with only the spacing differing, because this one closes a column
   of prose rather than following a note.

   No caption and no translation. The identifiers and the string literal stay English
   in both languages, exactly as they would in a repository; translating the inside of
   a code block would be the tell that it is decoration rather than code. */
.editorial .bio-query { margin: 28px 0 4px; max-width: 100%; min-width: 0; }
.editorial .bio-query pre { padding: 20px 22px; font-size: 12.5px; line-height: 1.7; }
/* A grid item defaults to min-width:auto, so a <pre> full of unbreakable lines
   widens its own track -- and because the body clips horizontally, the column is
   silently cut off on a phone instead of showing a scrollbar. The track has to be
   allowed to shrink below its content before overflow-x on the block can do its job. */
.editorial .about__text { min-width: 0; }
