/* Chumley iridescent theme layer. Loads after styles.css and only overrides
   material: lighting, glass and the 3D glossy objects. Layout, type scale and
   the data surfaces are deliberately left to styles.css so dense content keeps
   its contrast. Delete the <link> in index.html to revert. */

/* Registered so the iridescent film can be animated as one inherited value:
   html drives --spin, every conic gradient below reads it. */
@property --spin{syntax:'<angle>';inherits:true;initial-value:0deg}
@keyframes iri-spin{to{--spin:360deg}}

:root{
  --bg:#080510;
  --panel:#1a1426;
  --panel2:#211930;

  /* Iridescent film stops. The cool blue and cyan are what stop this reading
     as "a purple gradient" and start it reading as light on a curved surface. */
  --iri-blue:#4e7bff;
  --iri-violet:#8b5cf6;
  --iri-orchid:#c17aec;
  --iri-magenta:#e879c7;
  --iri-cyan:#7dd8f0;
  --iri-film:conic-gradient(from var(--spin),
    var(--iri-blue),var(--iri-violet),var(--iri-magenta),
    var(--iri-cyan),var(--iri-orchid),var(--iri-blue));

  /* Glass recipe */
  --glass:rgba(255,255,255,.055);
  --glass-strong:rgba(255,255,255,.085);
  --glass-edge:rgba(255,255,255,.115);
  --glass-blur:blur(26px) saturate(155%);
  --rim:inset 0 1px 0 rgba(255,255,255,.16);

  /* Depth is coloured light, never black */
  --glow-violet:0 24px 64px -22px rgba(139,92,246,.55);
  --glow-orchid:0 18px 50px -18px rgba(193,122,236,.5);
  --shadow:0 30px 80px -34px rgba(10,4,24,.9);
}
html{animation:iri-spin 30s linear infinite}

/* ---------------------------------------------------------------- 1. Ground */
html{background:var(--bg)}
body{background:transparent;position:relative}
body:before{
  content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(26% 20% at 4% -2%,rgba(110,150,255,.34),transparent 72%),
    radial-gradient(64% 50% at 12% 6%,rgba(78,123,255,.30),transparent 70%),
    radial-gradient(54% 42% at 94% -2%,rgba(193,122,236,.26),transparent 72%),
    radial-gradient(70% 56% at 86% 98%,rgba(139,92,246,.34),transparent 68%),
    radial-gradient(52% 42% at 22% 92%,rgba(232,121,199,.20),transparent 74%),
    radial-gradient(46% 40% at 58% 44%,rgba(139,92,246,.13),transparent 72%),
    var(--bg);
}

/* --------------------------------------------------------- 2. Glass chrome */
.sidebar{
  background:linear-gradient(180deg,rgba(31,22,48,.62),rgba(13,8,24,.74));
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  border-right:1px solid var(--glass-edge);
  box-shadow:inset -1px 0 0 rgba(255,255,255,.03),24px 0 70px -40px #000;
}
.topbar{
  background:linear-gradient(180deg,rgba(14,9,24,.74),rgba(14,9,24,.48));
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  border-bottom:1px solid var(--glass-edge);
  box-shadow:0 1px 0 rgba(255,255,255,.03);
}
.sidekick-rail{
  background:
    radial-gradient(130% 68% at 50% 0%,rgba(139,92,246,.4),transparent 62%),
    radial-gradient(80% 42% at 80% 100%,rgba(232,121,199,.14),transparent 70%),
    linear-gradient(180deg,rgba(255,255,255,.085),rgba(255,255,255,.025));
  backdrop-filter:blur(24px) saturate(150%);-webkit-backdrop-filter:blur(24px) saturate(150%);
  border:1px solid var(--glass-edge);
  box-shadow:var(--rim),var(--shadow);
}
dialog{
  background:linear-gradient(180deg,rgba(34,24,50,.88),rgba(19,12,32,.92));
  backdrop-filter:blur(30px) saturate(160%);-webkit-backdrop-filter:blur(30px) saturate(160%);
  border:1px solid var(--glass-edge);
  box-shadow:var(--rim),0 44px 140px -30px rgba(6,2,16,.95),var(--glow-violet);
}
.dialog-head{background:linear-gradient(180deg,rgba(38,27,56,.86),rgba(30,20,46,.7))}
.toast{
  background:linear-gradient(180deg,rgba(48,34,68,.86),rgba(30,20,46,.9));
  backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);
  border:1px solid var(--glass-edge);
  box-shadow:var(--rim),0 20px 60px -20px rgba(6,2,16,.9),var(--glow-orchid);
}
.menu-shade.visible{backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}

/* -------------------------------------------- 3. Solid surfaces under data */
/* These stay opaque on purpose: blur plus low-contrast fill plus 10-12px text
   is where this aesthetic starts failing contrast. They get the glass *edge*
   without the translucency. */
.panel{
  background:linear-gradient(180deg,#211a30,#1a1326);
  border-color:rgba(255,255,255,.075);
  box-shadow:var(--rim),0 20px 48px -30px rgba(6,2,16,.85);
}
.work-card,.lane,.day-cell,.sidekick-callout{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}
.work-card{background:#241a30}
.lane{background:#181121}
.day-cell{background:#1b1425}
.data-table th{background:rgba(255,255,255,.035);backdrop-filter:none;-webkit-backdrop-filter:none}
.task-row,.data-table td,.person-card,.activity-line{backdrop-filter:none;-webkit-backdrop-filter:none}

/* ------------------------------------------ 4. Flat tiles behind small icons */
/* These were bevelled, lit squircles: a white top edge, magenta bounce light off the bottom, a
   coloured drop shadow, and two pseudo-element sheen layers held at negative z. Flattened to a
   quiet tint and a 1px edge - which is what styles.css already asks for - so this section now
   mostly stops the theme modelling them rather than modelling them differently.
   The radii are restated unchanged on purpose: several of them exist only here, so dropping the
   block would resize the tiles as well as flatten them, and nothing around them should move. */
.metric-icon,.attention-icon,.pillar-symbol,.project-icon,
.dropzone .upload-icon,.file-info .file-glyph{
  position:relative;overflow:hidden;
  background:#b77aea16;
  border:1px solid rgba(255,255,255,.085);
  color:#c990ed;
  box-shadow:none;
}
/* The sheen layers are switched off rather than deleted, so what was removed stays legible next
   to what replaced it. */
.metric-icon:before,.attention-icon:before,.pillar-symbol:before,.project-icon:before,
.dropzone .upload-icon:before,
.metric-icon:after,.attention-icon:after,.pillar-symbol:after,.project-icon:after,
.dropzone .upload-icon:after{content:none}
.metric-icon>.icon,.attention-icon>.icon,.project-icon>.icon,
.dropzone .upload-icon>.icon{position:relative}
.metric-icon{border-radius:10px}
.attention-icon{border-radius:12px}
.pillar-symbol{border-radius:9px}
.project-icon{border-radius:13px}
.dropzone .upload-icon{border-radius:14px}

/* --------------------------------------- 5. Ambient bloom behind the logos */
/* Deliberately no sphere here: a lit iridescent orb behind the petal mark
   fought with the mark instead of framing it. Soft single-hue light only, so
   the logo reads as the object and nothing prismatic sits behind it. */
.sidekick-visual{isolation:isolate}
.sidekick-visual:before{
  width:138px;height:138px;filter:blur(30px);
  background:radial-gradient(circle,rgba(178,110,224,.4),rgba(126,84,214,.16) 54%,transparent 74%);
}
.auth-art{isolation:isolate}
.orb-stage{isolation:isolate}
.auth-art:before,.orb-stage:before{
  content:'';position:absolute;z-index:-1;pointer-events:none;border-radius:50%;
  background:radial-gradient(circle,rgba(178,110,224,.36),rgba(126,84,214,.15) 54%,transparent 74%);
}
.auth-art:before{width:240px;height:240px;left:50%;top:50%;transform:translate(-50%,-50%);filter:blur(42px)}
.orb-stage:before{width:170px;height:170px;left:50%;top:50%;transform:translate(-50%,-50%);filter:blur(34px)}

.boot{position:relative;isolation:isolate}
.boot:before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  left:50%;top:50%;width:360px;height:360px;transform:translate(-50%,-50%);
  border-radius:50%;filter:blur(46px);
  background:radial-gradient(circle,rgba(178,110,224,.34),rgba(126,84,214,.14) 54%,transparent 74%);
}

/* -------------------------------------------------------- 6. Flat avatars */
/* Was a lit sphere: a specular highlight at 32%/24%, a violet body gradient, an inner top edge,
   magenta bounce light and a drop shadow. Now one flat tint.
   Two things are kept deliberately. The 2px border stays, because in an avatar stack the discs
   overlap and the border is the only thing separating them - without it a row of people reads as
   one blob. And the initials keep a 9:1 contrast against the fill, which is not excessive at 9px:
   below about 7:1 two-letter type this small starts to smudge on a dark ground whatever the ratio
   says.
   The border is a faint purple rather than the panel ground. Darkening it to the ground made the
   ring invisible on a solo avatar, so the disc dissolved into the panel behind it - and a solo
   avatar is the common case: the stack only appears on a project header. Same hue as --line2, the
   edge every other bordered surface uses, a little stronger because 2px of it has to read at
   29px. It is opaque so the ring looks the same over a panel and over the avatar it overlaps in a
   stack, which a translucent one would not. */
.avatar{
  background:#332a47;
  color:#ddd0ef;
  border-color:#5c4a7a;
  box-shadow:none;
}

/* ------------------------------------------- 7. Glossy and glass controls */
.btn{
  background:rgba(255,255,255,.06);
  border-color:rgba(255,255,255,.105);
  backdrop-filter:blur(12px) saturate(140%);-webkit-backdrop-filter:blur(12px) saturate(140%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.09);
}
.btn:hover{background:rgba(255,255,255,.105)}
/* ------------------------------------------- 7b. The specular primary CTA */
/* A port of reactbits' Specular Button: a dark base stroke round the whole edge, and a bright
   streak on the sides facing a light that steers toward the pointer. See public/specular.mjs for
   why it is a port and not an install - the original needs a WebGL canvas per button.
   The streak is symmetric, two bright poles half a turn apart, because the shader takes the
   absolute value of the dot product with the light vector: the edge facing away catches it too.

   This replaces a flat lavender fill with dark type, chosen at the time because light type on a
   dark ground at 10-11px renders visibly thinner than its contrast ratio suggests. That trade is
   made deliberately here, by the owner, in favour of the reference's look. Two things push back
   against it as far as the design allows: the label keeps 600 weight rather than the reference's
   500, and it keeps its letter-spacing - both of which buy back some of the thinning.

   The rings are drawn on ::before and ::after with the padding-box mask trick rather than as a
   real border, because a border cannot hold a gradient that rotates. inset:-1px reaches from the
   padding box back out to the border box, and border-radius is specified on the border box, so
   `inherit` lines the rings up with the button's own outer edge exactly. */
/* inherits:true is load-bearing, not a default left alone. specular.mjs can only set these on the
   button, and the rings are its ::before and ::after - which are children. Registered as
   non-inheriting the values never reach them, and the rim sits frozen at its initial angle while
   the numbers on the element update perfectly, which looks exactly like a working feature. */
@property --spec-a{syntax:'<number>';inherits:true;initial-value:135}
@property --spec-b{syntax:'<number>';inherits:true;initial-value:0}

/* The body carries a faint grey tint rather than being fully transparent. That is the reference's
   own knob - it exposes `tint` and `tintOpacity` for exactly this and simply defaults the opacity
   to zero - and without it the button had no surface of its own, so it read as a rim floating on
   whatever was behind it.
   14% white is chosen against two neighbours, not one. Against the page it lands at 1.45:1, where
   the app's own panels sit only 1.09:1 above the page - so the button reads as an object rather
   than another surface. Against the secondary .btn, which already carries 6% white, it is 1.27:1,
   which is what keeps the two apart now that the primary no longer has a fill colour of its own
   to tell them apart. Matching the secondary's 6% would land at 1.00:1: the same button twice. */
.btn.primary{
  position:relative;
  color:#f5f5f5;
  font-weight:600;
  letter-spacing:.15px;
  background:rgba(255,255,255,.14);
  border-color:transparent;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 8px 24px rgba(0,0,0,.25);
  transition:transform .15s ease,background-color .18s;
}
.btn.primary:hover{
  background:rgba(255,255,255,.2);border-color:transparent}
.btn.primary:active{transform:scale(.97)}
.btn.primary .icon{position:relative}

.btn.primary::before,.btn.primary::after{
  content:'';position:absolute;inset:-1px;border-radius:inherit;padding:1px;pointer-events:none;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
}
/* The base stroke: always there, so the button has a shape before any light reaches it. */
.btn.primary::before{background:#6d5b88}
/* The streak. --spec-a is degrees and --spec-b is 0-1, both written by specular.mjs; the values
   above are what an untouched button shows, so the rim is correct before any script runs. */
.btn.primary::after{
  /* Stop positions follow the shader's window: a tight bright core (shineSize 10 degrees) that
     falls away by fifty (shineFade 40). A wider band lights the whole ring evenly and stops
     reading as a light source at all. */
  background:conic-gradient(from calc(var(--spec-a)*1deg),
    #fff 0deg,#ffffffd9 9deg,#ffffff00 50deg,#ffffff00 130deg,#ffffffd9 171deg,
    #fff 180deg,#ffffffd9 189deg,#ffffff00 230deg,#ffffff00 310deg,#ffffffd9 351deg,#fff 360deg);
  opacity:calc(.34 + var(--spec-b)*.66);
  transition:opacity .28s ease;
}
/* The hero and the sign-in form used to round their buttons into full pills while every other
   button on the page was a 9px rectangle, so the same control changed shape depending on where it
   sat. One radius everywhere; the base rule already sets it. */
.btn.ghost:hover{background:rgba(255,255,255,.075);border-color:rgba(255,255,255,.09)}

.search-button,.sidebar-profile,.badge,.tab,.kbd,.sidekick-input,
.input-icon input,.chat-composer textarea{
  background:rgba(255,255,255,.055);
  border-color:rgba(255,255,255,.1);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}
.search-button,.tab,.badge,.kbd{border-radius:999px}
.search-button{backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.search-button:hover{background:rgba(255,255,255,.09)}
.sidebar-profile{border-radius:13px}
.tab.active{
  background:linear-gradient(180deg,rgba(216,167,242,.3),rgba(148,85,235,.18));
  border-color:rgba(255,255,255,.2);color:#f0dcfd;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 6px 16px -8px rgba(139,92,246,.7);
}
.nav-link{border-radius:11px}
.nav-link.active{
  background:linear-gradient(100deg,rgba(193,122,236,.26),rgba(78,123,255,.1));
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.1),inset 0 1px 0 rgba(255,255,255,.16),
    0 8px 20px -12px rgba(139,92,246,.8);
}
.nav-link.active:before{
  background:linear-gradient(180deg,var(--iri-orchid),var(--iri-violet));
  box-shadow:0 0 12px rgba(193,122,236,.9);
}
.progress{background:rgba(255,255,255,.06);box-shadow:inset 0 1px 2px rgba(6,2,16,.6)}
.progress>span{
  background:linear-gradient(90deg,var(--iri-blue),var(--iri-violet) 46%,var(--iri-orchid));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.4),0 0 12px -2px rgba(193,122,236,.8);
}
.timeline-bar{
  background:linear-gradient(180deg,#ac7cea,#7c4fd6);
  border-color:rgba(255,255,255,.28);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.38),0 5px 14px -7px rgba(124,58,237,.85);
}
/* The row has no side padding - its icon and arrow line up with the text above them - so a hover
   fill on the row itself stopped flush against the icon with square corners and read as cut off.
   The fill is a rounded pill behind the row instead, reaching past both ends and kept clear of the
   dividers, so nothing about the row's own layout moves. */
.suggestion{position:relative;isolation:isolate}
.suggestion:before{content:'';position:absolute;inset:3px -8px;z-index:-1;border-radius:9px;background:var(--hover);opacity:0;transition:opacity .15s}
.suggestion:hover:before,.suggestion:focus-visible:before{opacity:1}
.prompt-grid button{
  background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.1);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}
.prompt-grid button:hover{background:rgba(255,255,255,.09)}

/* ------------------------------------------------- 8. Lit container edges */
.hero{
  position:relative;
  background:
    radial-gradient(78% 100% at 84% 62%,rgba(139,92,246,.6),transparent 62%),
    radial-gradient(64% 84% at 4% 4%,rgba(78,123,255,.34),transparent 66%),
    radial-gradient(40% 60% at 50% 0%,rgba(232,121,199,.16),transparent 70%),
    linear-gradient(150deg,rgba(255,255,255,.11),rgba(255,255,255,.025) 42%,rgba(255,255,255,0));
  backdrop-filter:blur(18px) saturate(145%);-webkit-backdrop-filter:blur(18px) saturate(145%);
  border-color:transparent;
  box-shadow:var(--rim),inset 0 -1px 0 rgba(255,255,255,.05),var(--shadow);
}
.hero:before{
  content:'';position:absolute;inset:0;z-index:2;padding:1px;pointer-events:none;
  border-radius:inherit;background:var(--iri-film);opacity:.62;
  -webkit-mask:linear-gradient(#000,#000) content-box,linear-gradient(#000,#000);
  -webkit-mask-composite:xor;mask:linear-gradient(#000,#000) content-box,linear-gradient(#000,#000);
  mask-composite:exclude;
}
.hero-copy{position:relative;z-index:3}
.file-preview{
  background:
    radial-gradient(ellipse at 50% 42%,rgba(139,92,246,.3),transparent 66%),
    linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.01));
  border-bottom:1px solid var(--glass-edge);
}
.dropzone{
  border-color:rgba(255,255,255,.16);
  background:linear-gradient(110deg,rgba(78,123,255,.06),rgba(232,121,199,.06));
}
.dropzone.dragover{border-color:rgba(216,167,242,.55);background:linear-gradient(110deg,rgba(139,92,246,.16),rgba(232,121,199,.13))}
.focus-widget{background:radial-gradient(ellipse at top,rgba(139,92,246,.16),transparent 74%),var(--panel)}
.auth-screen{
  background:
    radial-gradient(30% 26% at 8% 22%,rgba(110,150,255,.30),transparent 72%),
    radial-gradient(62% 50% at 16% 34%,rgba(78,123,255,.30),transparent 68%),
    radial-gradient(58% 46% at 80% 78%,rgba(193,122,236,.28),transparent 70%),
    radial-gradient(44% 36% at 92% 8%,rgba(139,92,246,.22),transparent 74%),
    var(--bg);
}
.auth-visual{border-right:1px solid var(--glass-edge)}
.auth-form-wrap{
  background:
    radial-gradient(70% 52% at 100% 0%,rgba(139,92,246,.22),transparent 68%),
    linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,0));
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
  border-left:1px solid rgba(255,255,255,.05);
}

/* ------------------------------------------------ 8b. Lightfall in the hero */
/* The shader draws light on black, so it is composited with screen: black
   contributes nothing and only the streaks add to the card's own gradient.
   .hero already isolates via backdrop-filter; isolate explicitly so the blend
   can never reach past the card. */
.hero{isolation:isolate}
.hero-lightfall{
  position:absolute;inset:0;z-index:0;
  border-radius:inherit;overflow:hidden;
  pointer-events:none;              /* clicks belong to the copy and the CTA */
  mix-blend-mode:screen;opacity:.5;
  /* Weighted to the right, behind the art. At full strength across the card
     the streaks cut straight through the headline and body copy; this keeps
     the effect where there is nothing to read. Same trick .hero:after uses. */
  -webkit-mask-image:linear-gradient(90deg,transparent 4%,rgba(0,0,0,.18) 30%,rgba(0,0,0,.6) 52%,#000 78%);
  mask-image:linear-gradient(90deg,transparent 4%,rgba(0,0,0,.18) 30%,rgba(0,0,0,.6) 52%,#000 78%);
}
.hero-lightfall canvas{display:block;width:100%;height:100%}
/* PixelBlast takes the slot Lightfall leaves empty. Lightfall is hidden in light mode because its
   streaks are additive and vanish on a pale ground; this one paints solid pixels and only works
   the other way round, so each theme gets the one it can actually show. Hidden by default and
   turned on for light, so a theme added later inherits nothing it did not ask for.
   Masked to the right like Lightfall, and for the same reason: at full strength across the card a
   field of pixels runs straight under the headline and makes it harder to read. */
.hero-pixelblast{
  display:none;
  position:absolute;inset:0;z-index:0;
  border-radius:inherit;overflow:hidden;
  pointer-events:none;              /* clicks belong to the copy and the CTA */
  /* Strong enough for a 3px block to keep its colour. Below this the typical dot is so diluted by
     the pale ground behind it that it reads as grey dust rather than as anything purple - the cores
     were always the right hue, but almost none of the field is core. */
  opacity:.92;
  /* The mask keeps the field off the headline, and it was doing far more than that: at 32% across
     it held the effect to 14% of itself, and a 3px block that darkens the ground by nine levels out
     of 255 has no colour left to read - the eye calls any mark that faint grey, whatever hue it is.
     It clears the copy by 30% of the width and then comes up quickly. */
  -webkit-mask-image:linear-gradient(90deg,transparent 10%,rgba(0,0,0,.06) 38%,rgba(0,0,0,.8) 58%,#000 74%);
  mask-image:linear-gradient(90deg,transparent 10%,rgba(0,0,0,.06) 38%,rgba(0,0,0,.8) 58%,#000 74%);
}
.hero-pixelblast canvas{display:block;width:100%;height:100%}
:root[data-theme="light"] .hero-pixelblast{display:block}

/* ShapeWaves takes the same slot on the same terms, and only one of the two is ever in the tree:
   app.mjs renders this one where WebGPU looks possible and the PixelBlast one where it does not,
   so a reader on a browser without WebGPU still gets a moving banner rather than a bare gradient.
   Same mask as above, for the same reason - a field of shapes running under the headline makes it
   harder to read - but no opacity of its own: the shapes carry their own coverage and dimming the
   whole layer would only push them back towards the grey this was moved away from. */
.hero-shapewaves{
  display:none;
  position:absolute;inset:0;z-index:0;
  border-radius:inherit;overflow:hidden;
  pointer-events:none;              /* clicks belong to the copy and the CTA */
  -webkit-mask-image:linear-gradient(90deg,transparent 10%,rgba(0,0,0,.06) 38%,rgba(0,0,0,.8) 58%,#000 74%);
  mask-image:linear-gradient(90deg,transparent 10%,rgba(0,0,0,.06) 38%,rgba(0,0,0,.8) 58%,#000 74%);
}
/* The device is fetched asynchronously, so the canvas is in the tree for a moment before it has
   anything on it. Fading in on the first drawn frame keeps that from reading as a flash. */
.hero-shapewaves canvas{display:block;width:100%;height:100%;opacity:0;transition:opacity 600ms cubic-bezier(.22,1,.36,1)}
.hero-shapewaves canvas[data-ready="true"]{opacity:1}
:root[data-theme="light"] .hero-shapewaves{display:block}

/* Both masks are percentages of the card, which is right on a wide banner - the copy takes the
   left half and the field has the rest - and wrong on a phone, where the copy takes nearly all of
   it and the field ends up behind the words. Measured at 400px wide, that put the body copy at
   2.45:1 against its background under ShapeWaves and 3.00:1 under PixelBlast, both under the 4.5:1
   that normal-size text needs. The copy runs to 78.2% of the card at 400px wide, so below the
   breakpoint the field stays fully transparent past that and keeps only the strip beside it. The
   stop has to clear the longest line outright rather than merely start faint over the last word,
   which is the same bug with a smaller number. */
@media (max-width:800px){
  .hero-shapewaves,.hero-pixelblast{
    -webkit-mask-image:linear-gradient(90deg,transparent 80%,rgba(0,0,0,.3) 90%,#000 97%);
    mask-image:linear-gradient(90deg,transparent 80%,rgba(0,0,0,.3) 90%,#000 97%);
  }
}

/* --------------------------------------------------------------- 9. Detail */
/* Scrollbars. Every one in the product, so this is the only place they are described -
   the core sheet used to set the width and this layer the colour, which left the track, the
   corner where two bars meet, and every non-WebKit browser unaccounted for.

   A WebKit thumb cannot be inset with padding; a transparent border plus background-clip is the
   only way. So the bar reserves 12px and paints a 6px pill down the middle of it - the same
   width of pill as before, with room around it. Without that the thumb runs hard against the
   edge of whatever is scrolling, and in a dialog that means a bright line drawn straight across
   an 18px rounded corner, squaring it off.

   Chrome ignores every ::-webkit-scrollbar rule on an element that sets scrollbar-width or
   scrollbar-color, so the standard properties cannot just be listed alongside these: they would
   switch Chrome to its own thin scrollbar and discard all of it. That is not hypothetical - it
   is why .live-transcript, which set scrollbar-width:thin, was the one panel in the product
   showing a default grey scrollbar. They go behind a support query instead, for engines with no
   webkit pseudo-elements at all. .sidebar still hides its own with scrollbar-width:none, which
   outranks the star selector below and is the one thing both syntaxes agree on. */
::-webkit-scrollbar{width:12px;height:12px}
::-webkit-scrollbar-track{background:transparent}
/* background-color, not the background shorthand: the shorthand resets background-clip and the
   inset would quietly disappear on hover. */
::-webkit-scrollbar-thumb{
  background-color:rgba(198,160,235,.24);
  border:3px solid transparent;background-clip:padding-box;
  border-radius:999px;
}
::-webkit-scrollbar-thumb:hover{background-color:rgba(212,175,245,.42)}
::-webkit-scrollbar-thumb:active{background-color:rgba(222,190,250,.58)}
/* Where a vertical and a horizontal bar meet. Unpainted it is a pale square in a dark panel. */
::-webkit-scrollbar-corner{background:transparent}
@supports not selector(::-webkit-scrollbar){
  *{scrollbar-width:thin;scrollbar-color:rgba(198,160,235,.32) transparent}
}
::selection{background:rgba(139,92,246,.42);color:#fff}
.pillar-card:hover,.project-card:hover,.work-card:hover,.work-tile:hover{
  box-shadow:var(--rim),0 16px 40px -22px rgba(139,92,246,.6);
}


/* ------------------------------------------------------- 10. Small screens */
@media(max-width:1100px){
}
@media(max-width:800px){
  /* Stacked layout puts the sidebar over content, so it needs to be opaque
     enough to read against whatever is behind it. */
  .sidebar{background:linear-gradient(180deg,rgba(26,18,42,.94),rgba(12,7,22,.97))}
  .auth-form-wrap{
    background:
      radial-gradient(ellipse at top,rgba(139,92,246,.28),transparent 70%),
      linear-gradient(180deg,rgba(255,255,255,.03),rgba(255,255,255,0));
    border-left:0;
  }
}
@media(max-width:800px){
  /* styles.css drops the chat mark and its orb stage here too. */
  .orb-stage:before{width:132px;height:132px}
}
@media(max-width:520px){
  .auth-art:before{width:186px;height:186px}
}

/* Reduced motion is already forced off with !important in styles.css; this
   just makes the static frame of the film a deliberate one. */
@media(prefers-reduced-motion:reduce){:root{--spin:212deg}}


/* ------------------------------------------------------------ 11. Light mode
   Set as data-theme="light" on <html> by applyTheme() in app.mjs, and never by a media query
   alone: 'system' is the default but an explicit choice has to be able to beat the machine.

   This layer, not styles.css, is where light lives - because this layer is a second complete
   palette. It hardcodes the surfaces on .panel, .sidebar, .topbar, dialog, .btn, .tab and
   .nav-link, so redefining the tokens in styles.css:root alone produces a light page with dark
   glass chrome painted over it, which reads as "the tokens are broken" rather than "there are
   two palettes". Everything overridden below is something this file, or styles.css, had already
   fixed to a dark value.

   The recurring shape is an overlay: white at a low alpha over a dark ground. In light mode the
   ink has to flip while the alphas stay, which is what --tint is for - the colour that gets
   washed over a surface to lift or lower it. Dark tint on light, light tint on dark.

   Two rules that a first version of this section got wrong, both worth stating so they are not
   re-introduced:

   1. A contrast floor is not a palette. Deriving every text colour by darkening it until it
      cleared 4.9:1 and stopping there put 61 of 62 colours between 4.87:1 and 4.93:1 - so a work
      item's title, its dependency count and its date all came out the same weight, and the page
      lost the hierarchy that tells you what to read first. Section 11.5 derives from the dark
      theme's own spread instead. See the note there.

   2. Inverting a surface means inverting the figure with it. The primary button is a pale fill
      with dark ink on the dark theme; the honest light-mode equivalent is a saturated fill with
      white ink, not the same pale fill left in place. Turned into a light grey it stopped
      reading as the primary action at all - every call to action on the page looked disabled. */

/* --------------------------------------------------------------------- 11.1 Tokens */
:root[data-theme="light"]{
  color-scheme:light;

  --bg:#f4f1f9;
  --sidebar:#ffffff;
  --panel:#ffffff;
  --panel2:#f7f4fc;
  --hover:#efe9f8;

  /* Lines carry the page's own hue rather than grey, or the whole thing reads as a wireframe. */
  --line:rgba(58,28,92,.11);
  --line2:rgba(58,28,92,.2);

  --text:#1b1327;
  --muted:#5d5170;
  --faint:#6f6482;

  /* The dark palette's accents are tuned to glow against near-black and go weak and candied on
     white. These are the same hues taken darker, so purple still reads as purple and the status
     colours still clear 4.5:1 against a white panel. */
  --accent:#8639c8;
  --accent2:#6d2fbe;
  --purple:#7434bb;
  --green:#12735a;
  --amber:#8a5510;
  --red:#b02a48;
  --blue:#2554ad;

  --tint:#2a1240;
  --glass:color-mix(in srgb,var(--tint) 3.5%,transparent);
  --glass-strong:color-mix(in srgb,var(--tint) 6%,transparent);
  --glass-edge:color-mix(in srgb,var(--tint) 13%,transparent);
  --glass-blur:blur(22px) saturate(130%);
  /* A rim catches light from above. On a dark surface that is a white hairline; on a light one
     the equivalent is a soft shadow under the top edge, not a brighter white. */
  --rim:inset 0 1px 0 rgba(255,255,255,.9),inset 0 0 0 1px rgba(58,28,92,.05);

  /* No drop shadows on the light theme, by decision. A shadow is an absence of light, so on a
     dark ground it is nearly free - there is little light to take away. On a white one the same
     gesture has to darken the page to be seen at all, and a grid of cards each sitting in its own
     grey smudge is what that costs. Separation comes from the border and the panel being whiter
     than the ground instead.
     These four are the tokens the rest of the sheet reaches for, so emptying them here retires
     every shadow that flows through them - .panel, .hero, .sidekick-rail, dialog, the popup - and
     the named overrides below only have to deal with the ones written as literals. Insets stay
     throughout: a rim light is not a drop shadow. */
  --glow-violet:none;
  --glow-orchid:none;
  --shadow:none;
  --lift:none;
  --lift-high:none;
}

/* ------------------------------------------------------- 11.2 The ground and the chrome */
/* The shadows the tokens cannot reach, because these are written as literals in styles.css or in
   section 2 above. Found by reading every computed box-shadow on every route rather than by
   grepping this file - which is how the sidebar turned up: it carries a pure black
   24px 0 70px -40px #000, the darkest thing on the light page, and nothing in the light palette
   went anywhere near it.
   Insets are preserved where they exist. Where a shadow was the only thing separating a surface
   from what is behind it, the border takes over. */
:root[data-theme="light"] .sidebar{box-shadow:none}
:root[data-theme="light"] .toast{box-shadow:none;background:#ffffff;border-color:var(--line2);color:var(--text)}
:root[data-theme="light"] .rt-ghost{box-shadow:none}
:root[data-theme="light"] .timeline-bar{box-shadow:inset 0 1px 0 rgba(255,255,255,.38)}
/* Card hover was a shadow growing underneath. With no shadow to grow it needs to be something
   else, or hovering a card stops answering - so the edge takes the accent and the fill lifts a
   shade. Both are cheap to see and neither darkens the page. */
:root[data-theme="light"] .pillar-card:hover,
:root[data-theme="light"] .project-card:hover,
:root[data-theme="light"] .work-card:hover,
:root[data-theme="light"] .work-tile:hover{
  box-shadow:none;border-color:rgba(109,47,190,.34);background:#fdfcfe;
}

/* Painted on <html> because body is transparent in this theme - a light mode that paints body is
   overpainted by the root and looks like it did nothing. */
:root[data-theme="light"] body:before{
  background:
    radial-gradient(26% 20% at 4% -2%,rgba(110,150,255,.1),transparent 72%),
    radial-gradient(58% 46% at 96% 0%,rgba(193,122,236,.12),transparent 70%),
    radial-gradient(50% 40% at 50% 100%,rgba(125,216,240,.08),transparent 74%);
}

:root[data-theme="light"] .topbar{
  background:linear-gradient(180deg,rgba(255,255,255,.88),rgba(255,255,255,.7));
  border-bottom:1px solid var(--line);
}
:root[data-theme="light"] .sidebar{background:var(--sidebar);border-right:1px solid var(--line)}
:root[data-theme="light"] .panel{background:var(--panel);border:1px solid var(--line);box-shadow:none}
:root[data-theme="light"] dialog{
  background:linear-gradient(180deg,#ffffff,#faf8fe);
  border:1px solid var(--line2);
  /* A dialog is already separated from the page by its dimmed backdrop, which does the job a
     drop shadow would have done and does not tint anything behind it. */
  box-shadow:none;
}
:root[data-theme="light"] .dialog-head{background:rgba(255,255,255,.96)}
:root[data-theme="light"] .dialog-foot{background:rgba(255,255,255,.97)}
:root[data-theme="light"] dialog::backdrop{background:rgba(28,14,48,.32)}

:root[data-theme="light"] .nav-link{color:#544a63}
:root[data-theme="light"] .nav-link:hover{background:color-mix(in srgb,var(--tint) 5%,transparent);color:var(--text)}
:root[data-theme="light"] .nav-link.active{
  background:linear-gradient(100deg,rgba(134,57,200,.13),rgba(78,123,255,.06));
  box-shadow:inset 0 0 0 1px rgba(109,47,190,.14);
  color:var(--accent2);
}
:root[data-theme="light"] .nav-link.active .icon{color:var(--accent)}

/* mix-blend-mode:screen adds light to what is under it. Over a white hero it adds nothing at
   all - the canvas still renders, still holds a WebGL context and still animates, invisibly.
   lightfall.mjs does have a light branch but its uniform is set once at init and its location is
   never stored, so there is no runtime way to reach it. Hiding it is honest; pretending it works
   is not. */
:root[data-theme="light"] .hero-lightfall{display:none}
:root[data-theme="light"] .hero{
  background:radial-gradient(ellipse at 78% 60%,rgba(134,57,200,.14),transparent 56%),
             linear-gradient(115deg,#faf7ff,#f2ecfb 80%);
  border:1px solid rgba(58,28,92,.12);
}
:root[data-theme="light"] .hero h2{color:#241a33}

/* The mark is mid-purple and it sits on a pale purple field of the same hue, which is why it read
   as part of the pattern. The field is cleared around it in the shader; this is the other half.
   Deepening the mark itself moved its edge contrast against the ground immediately around it from
   2.26:1 to 3.31:1 - by far the largest lever of the ones measured. A drop shadow was tried first
   and made it worse (2.10:1): the mark is dark on a light ground, so darkening the ground beside
   it closes the gap rather than opening it. A white halo reached 2.63:1 alone and added nothing
   once the mark was deeper, so it is not here. Light mode only; the glow is what the dark banner
   needs and is kept in the chain because replacing `filter` replaces all of it. */
:root[data-theme="light"] .hero-art img{filter:brightness(.76) saturate(1.25) drop-shadow(0 0 32px #ca7dff3d)}


/* The wordmark is a PNG of pure white pixels, so nothing in the cascade can recolour it - no
   token, no currentColor. brightness(0) turns every one of those pixels black, and the opacity
   takes it back to the near-black the rest of the page uses. */
:root[data-theme="light"] .brand .wordmark{filter:brightness(0);opacity:.82}

/* ------------------------------------------------------- 11.3 Buttons and controls */
/* Section 7 gives every .btn a white-at-6% fill and a white hairline, which is nothing at all
   over a white page - a secondary button arrives as bare text sitting next to a filled one.
   The theme scope is wrapped in :where() so it adds no specificity: this has to beat section 7's
   bare .btn without also beating .btn.ghost, .btn.text or .view-toggle .btn, all of which set
   background deliberately and are only one class more specific. Raising the specificity here
   would have given the ghost buttons a fill and filled in the segmented control's empty half. */
:where(:root[data-theme="light"]) .btn{
  background:color-mix(in srgb,var(--tint) 4%,#ffffff);
  border-color:var(--line2);
  box-shadow:none;
}
:root[data-theme="light"] .btn:hover{background:color-mix(in srgb,var(--tint) 6%,transparent)}
/* Delete. #45223166 is a dark maroon at 40% alpha, which over white composites to a grey-mauve
   rather than to anything that reads as danger. */
:root[data-theme="light"] .btn.danger{
  background:color-mix(in srgb,var(--red) 8%,#ffffff);
  border-color:color-mix(in srgb,var(--red) 32%,#ffffff);
}
:root[data-theme="light"] .btn.danger:hover{background:color-mix(in srgb,var(--red) 14%,#ffffff)}
:root[data-theme="light"] .btn.ghost:hover{background:color-mix(in srgb,var(--tint) 6%,transparent);border-color:var(--line2)}

/* The primary action, and the one thing on the page allowed to be loud. White on --accent2
   measures 7.5:1. The ::before/::after specular ring is deliberately NOT overridden here: it
   draws white light glancing off an edge, which is wrong on a pale button and right again on a
   saturated one, so the dark theme's own rule is the correct rule for this fill. */
:root[data-theme="light"] .btn.primary{
  background:var(--accent2);border-color:var(--accent2);color:#ffffff;
  box-shadow:0 1px 0 rgba(255,255,255,.2) inset;
}
:root[data-theme="light"] .btn.primary:hover{background:#7c3ccf;border-color:#7c3ccf}
:root[data-theme="light"] .btn.primary .icon{color:#ffffff}

/* Section 2 gives this whole group a white-at-5.5% fill and a white hairline, which on a white
   page is nothing at all: the pills lose their shape and the inputs lose their edge. Same
   recipe, tinted the other way. */
:root[data-theme="light"] .search-button,
:root[data-theme="light"] .sidebar-profile,
:root[data-theme="light"] .badge,
:root[data-theme="light"] .tab,
:root[data-theme="light"] .kbd,
:root[data-theme="light"] .sidekick-input,
:root[data-theme="light"] .sticky-new,
:root[data-theme="light"] .input-icon input,
:root[data-theme="light"] .chat-composer textarea{
  background:color-mix(in srgb,var(--tint) 4%,#ffffff);
  border-color:var(--line);
  box-shadow:none;
}
:root[data-theme="light"] .search-button:hover{background:color-mix(in srgb,var(--tint) 7%,#ffffff)}
:root[data-theme="light"] .chat-composer textarea{background:none;border:0}
:root[data-theme="light"] .tab.active{
  background:linear-gradient(180deg,rgba(134,57,200,.16),rgba(109,47,190,.09));
  border-color:rgba(109,47,190,.26);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9);
}

/* ...but not the underline variant, which is text with a rule under it and no box at all.
   `:root[data-theme="light"] .tab` is (0,3,0) - :root and the attribute count as two of those -
   and `.tabs.underline .tab` in the base sheet is also (0,3,0). A tie, and this file loads
   second, so the chip fill won and every Board/Table/Calendar strip rendered in light mode as a
   row of white blocks with the active one in a purple gradient. Dark was never affected, because
   none of these rules apply there.
   Restated at (0,4,0) and (0,5,0) so the variant wins where it should. The active and hover rules
   have to respell their underline colour: this rule clears border-bottom-color at (0,4,0), which
   would otherwise tie with the base sheet's own active and hover rules and erase the underline
   that is the entire point of the variant. */
:root[data-theme="light"] .tabs.underline .tab{
  background:none;box-shadow:none;border-bottom-color:transparent}
:root[data-theme="light"] .tabs.underline .tab:hover{border-bottom-color:rgba(109,47,190,.34)}
:root[data-theme="light"] .tabs.underline .tab.active{
  background:none;box-shadow:none;border-bottom-color:var(--accent2)}
:root[data-theme="light"] .search-button{background:#f6f3fb;border-color:var(--line2);color:var(--faint)}

/* Status badges. Each is a pale hue at 5-8% alpha over a dark panel, where that is enough to
   lift the pill off its ground. Over white the same wash is invisible and "At risk" arrives as
   an unfilled outline - the one badge on the page that should be impossible to skim past. Same
   idea, built from the light palette's own darker hue so the tint has something to tint with.
   The text colour is already handled: --green, --amber, --red are redefined in 11.1. */
:root[data-theme="light"] .badge.green{background:color-mix(in srgb,var(--green) 10%,#ffffff);border-color:color-mix(in srgb,var(--green) 28%,#ffffff)}
:root[data-theme="light"] .badge.amber{background:color-mix(in srgb,var(--amber) 10%,#ffffff);border-color:color-mix(in srgb,var(--amber) 28%,#ffffff)}
:root[data-theme="light"] .badge.red{background:color-mix(in srgb,var(--red) 9%,#ffffff);border-color:color-mix(in srgb,var(--red) 26%,#ffffff)}
:root[data-theme="light"] .badge.purple{background:color-mix(in srgb,var(--accent) 10%,#ffffff);border-color:color-mix(in srgb,var(--accent) 26%,#ffffff)}
:root[data-theme="light"] .badge.blue{background:color-mix(in srgb,var(--blue) 9%,#ffffff);border-color:color-mix(in srgb,var(--blue) 26%,#ffffff)}

/* The select arrow is a data URI with its colour URL-encoded inside it (%23...), which no custom
   property can reach into. The whole image is replaced rather than recoloured. */
:root[data-theme="light"] select{
  background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235d5170' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
}
/* :where() again, and for a sharper reason than the .btn rule. Written plainly this selector is
   (0,2,1) - two of those come from the theme scope alone - which outranks every state rule the
   product writes for an input: .dialog-title-input:focus and .cell-input:focus are both (0,2,0),
   so in light mode a focused field drew its resting grey underline instead of the accent. The
   palette should set what a control looks like at rest and lose to anything describing a state,
   which is what zero-specificity scoping buys. CI caught this; a dark-appearance laptop cannot. */
/* background-color, never the background shorthand. The shorthand also resets background-image,
   -repeat, -position and -size, and a select carries its chevron as a background image: the
   image itself survived on the rule above, which is more specific, but repeat/position/size are
   set nowhere in this file, so they fell back to this declaration's initial values. Equal
   specificity with the base sheet's select rule and this file loads second, so no-repeat became
   repeat and the chevron tiled across every select on the page in light mode. */
:where(:root[data-theme="light"]) input,
:where(:root[data-theme="light"]) select,
:where(:root[data-theme="light"]) textarea{background-color:#ffffff;border-color:var(--line2)}
/* The board's filter selects hardcode #1d1529, and .board-filter select is (0,1,1) - one class
   more than the :where()-scoped rule above, so the light palette never reached them. Their text
   is var(--text), which had already flipped to near-black, so they were near-black on near-black
   at 1.02:1: not low contrast, invisible. Only the surface needs correcting; once it is white
   the inherited --text is right. */
:root[data-theme="light"] .board-filter select{background-color:#ffffff;border-color:var(--line2)}
/* The date panel, which is in the top layer and so is not a descendant of anything the palette
   scopes - it has to be named directly, the same trap the select picker sets. */
:root[data-theme="light"] .daypick{
  background:#ffffff;border-color:var(--line2);color:#241a33;
  box-shadow:0 22px 60px rgba(40,20,70,.18);
}
:root[data-theme="light"] .daypick-day{color:#241a33}
:root[data-theme="light"] .daypick-day.is-outside{color:#9b8fae}
:root[data-theme="light"] .daypick-day:hover:not(:disabled){background:#f3ecfb}
:root[data-theme="light"] .daypick-day.is-today{border-color:#cdb6e4}
:root[data-theme="light"] .daypick-day.is-selected{background:#8639c8;border-color:#8639c8;color:#ffffff}
:root[data-theme="light"] .daypick-title{color:#241a33}
:root[data-theme="light"] button.daypick-title:hover{background:#f3ecfb;border-color:#cdb6e4}
:root[data-theme="light"] .daypick-cell{color:#241a33}
:root[data-theme="light"] .daypick-cell:hover:not(:disabled){background:#f3ecfb}
:root[data-theme="light"] .daypick-cell.is-today{border-color:#cdb6e4}
:root[data-theme="light"] .daypick-cell.is-selected{background:#8639c8;border-color:#8639c8;color:#ffffff}
:root[data-theme="light"] input[type="date"]{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235d5170' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cpath d='M16 2v4M8 2v4M3 10h18'/%3E%3C/svg%3E");
}
/* The open dropdown, where the browser supports handing it over. ::picker(select) is in the top
   layer and not a descendant of anything themed, so the light palette has to name it directly -
   a variable inherited from :root would not reach it. */
@supports (appearance:base-select){
  /* select::picker(select), not ::picker(select) on its own: the pseudo belongs to the select, so
     hung off the theme scope alone it asks for a picker on the root element and matches nothing.
     The options are still ordinary descendants and were styled correctly, which is how this got
     through - a light list of dark-on-dark rows with one readable row in it. */
  :root[data-theme="light"] select::picker(select){
    background:#ffffff;border-color:var(--line2);box-shadow:0 22px 60px rgba(40,20,70,.18);
  }
  :root[data-theme="light"] option{color:#241a33}
  :root[data-theme="light"] option:hover,:root[data-theme="light"] option:focus{background:#f3ecfb}
  :root[data-theme="light"] option:checked{color:#6d2291}
  :root[data-theme="light"] option::checkmark{color:#8639c8}
}
/* A focused table cell paints #1d1529 so it lifts off the dark row. On a white row that is a
   near-black box holding near-black text, and it appears only while you are typing in it. */
:root[data-theme="light"] .cell-input:focus{background-color:#ffffff}
:root[data-theme="light"] .checklist{background:var(--panel2)}
:root[data-theme="light"] .check-box{background:#ffffff;border-color:var(--line2)}
/* The completed state has to restate its fill, not just its tick. Both this and the plain
   .check-box rule above land on three classes, and this file loads after styles.css, so the
   white fill was winning on source order and a ticked item rendered as an empty white box - no
   green, and a #160e22 tick invisible on white either way. The tick itself flips because
   --green is #8cdcb9 on the dark theme and #12735a here: same name, opposite end of the scale. */
:root[data-theme="light"] .check-row.is-done .check-box{background:var(--green);border-color:var(--green);color:#ffffff}

/* The switch. Its track is the state, so the track is what has to carry the accent - a knob that
   only slides is a control you have to look twice at. */
:root[data-theme="light"] .toggle{background:#e6dff0;border-color:#c7b9d9}
/* The knob is white on a light track, so it needs an edge to exist at all. A hairline ring is
   that edge; the 1px shadow it replaces was doing the same job by darkening the track under it. */
:root[data-theme="light"] .toggle:after{background:#ffffff;box-shadow:0 0 0 1px rgba(58,28,92,.2)}
:root[data-theme="light"] .toggle[aria-checked=true]{background:var(--accent2);border-color:var(--accent2)}
:root[data-theme="light"] .toggle[aria-checked=true]:after{background:#ffffff}

/* A segmented control: a recessed trough with one raised segment in it. On the dark theme the
   raised segment is lighter than the trough; here it is white and the trough is tinted, which is
   the same relationship the other way up. */
:root[data-theme="light"] .view-toggle{background:color-mix(in srgb,var(--tint) 6%,#ffffff);border-color:var(--line)}
:root[data-theme="light"] .view-toggle .btn.active{
  background:#ffffff;box-shadow:0 0 0 1px rgba(58,28,92,.1);
}

:root[data-theme="light"] .progress{background:rgba(58,28,92,.09);box-shadow:inset 0 1px 2px rgba(40,18,72,.08)}
/* --green, --amber and --red are darkened in 11.1 so they work as TEXT on white, and a status
   bar reuses them as a fill. Those are different jobs with different bars: text needs 4.5:1,
   a meaningful graphic needs 3:1, and a colour tuned for the first comes out muddy at the
   second - an amber bar arrived as a brown stripe rather than a warning. These are the
   same hues taken back up until they are recognisably amber, green and red again, and no
   further: measured against the track (#ede9f0 over a white panel) they are 3.28:1, 3.34:1 and
   4.68:1, so the 3:1 floor still holds with room. */
:root[data-theme="light"] .progress.amber>span{background:#b57119}
:root[data-theme="light"] .progress.green>span{background:#0d9070}
:root[data-theme="light"] .progress.red>span{background:#c02e4f}
/* The lane count sits on its own tinted chip inside an already-tinted lane, so --faint measured
   4.48:1 against what is actually behind it - under the bar by a rounding error, which is still
   under the bar. --muted on the same chip measures 6.1:1. */
:root[data-theme="light"] .lane-title .count{background:rgba(58,28,92,.06);color:var(--muted)}

/* -------------------------------------- 11.4 Surfaces the base sheet paints dark
   Each of these is a hardcoded near-black in styles.css rather than a token, so nothing in 11.1
   reaches them and they survived as dark islands on a white page. They were found by walking the
   rendered DOM of every route and flagging any painted background whose measured luminance was
   below .18 - reading the stylesheet would have missed the ones that only exist behind a tab. */

/* The work table. This is the one that mattered most: .table-scroll painted the whole thing near
   black while the cell text had already flipped to near black, so the Work Item column was not
   low-contrast, it was invisible. */
:root[data-theme="light"] .table-scroll{background:#ffffff}
:root[data-theme="light"] .work-table th{background:#f7f4fc}
:root[data-theme="light"] .work-table .stage-row th{background:none}
:root[data-theme="light"] .work-table tbody tr:hover{background:#faf7fd}
:root[data-theme="light"] .work-table tbody tr.is-selected{background:#f2eafb}

/* The calendar grid. */
:root[data-theme="light"] .cal-head{background:#f7f4fc}
:root[data-theme="light"] .cal-cell{background:#ffffff}
:root[data-theme="light"] .cal-cell.weekend{background:#faf8fd}

/* The roadmap's frozen label column, and the ruled lines behind its tracks - both drawn as white
   at 4% alpha, which is exactly nothing over white. */
:root[data-theme="light"] .timeline-labels{background:#ffffff}
:root[data-theme="light"] .timeline-track{
  background:repeating-linear-gradient(90deg,transparent 0,transparent calc(100% / 28 - 1px),
    rgba(58,28,92,.07) calc(100% / 28 - 1px),rgba(58,28,92,.07) calc(100% / 28));
}

/* The delivery forecast Gantt is vendored and drives every colour through a --g- variable, so it
   converts by redefining them rather than by chasing its internals. The exception is the frame
   itself, which takes a literal background. */
:root[data-theme="light"] .gantt-frame{
  background:#ffffff;
  --g-bar-color:#e7ddf5;--g-bar-border:#c6b3e2;--g-arrow-color:#8d7ea8;
  --g-row-color:rgba(58,28,92,.014);--g-row-border-color:rgba(58,28,92,.08);
  --g-header-background:#ffffff;
  /* The vendor's own defaults are --g-text-dark:#171717 and --g-text-light:#fff, because these
     are two different jobs: ink on the page, and ink on something coloured. The dark theme
     collapses them to one pale value, which is right when both grounds are dark. Setting both to
     near-black here put black on the purple today pill at 2.9:1 - they have to separate again. */
  --g-text-dark:#1b1327;--g-text-light:#ffffff;--g-text-muted:#5d5170;
  --g-tick-color:rgba(58,28,92,.07);--g-tick-color-thick:rgba(58,28,92,.15);
  --g-today-highlight:#8639c8;
  --g-weekend-highlight-color:rgba(58,28,92,.03);--g-weekend-label-color:#7d7090;
  --g-progress-color:#b28ade;--g-actions-background:#ffffff;--g-popup-actions:#ffffff;
}
/* Bars are an SVG fill and stroke, not a background, so they need their own pass. Pale fill,
   saturated stroke: the same figure/ground relationship the dark theme uses, inverted. */
:root[data-theme="light"] .gantt-frame .bar-wrapper.bar-critical .bar{fill:#fadfe5;stroke:#b02a48}
:root[data-theme="light"] .gantt-frame .bar-wrapper.bar-active .bar{fill:#ecdefa;stroke:#8639c8}
:root[data-theme="light"] .gantt-frame .bar-wrapper.bar-pending .bar{fill:#efebf6;stroke:#a698bd}
:root[data-theme="light"] .gantt-frame .bar-wrapper.bar-blocked .bar{fill:#fbeed8;stroke:#8a5510}
/* Bar labels. The vendor draws a label inside its bar when it fits and adds .big to push it out
   onto the grid when it does not, and styles.css pins the inside case to a pale #f4eefc for the
   dark theme's saturated bars. Light-mode bars are pale fills, so inside and outside both want
   dark ink - which is also what makes this one rule enough to cover the vendor's own
   :is(.gantt .bar-invalid)~.bar-label, whose --g-text-light would otherwise be white on white. */
:root[data-theme="light"] .gantt-frame .bar-label{fill:var(--g-text-dark)}
:root[data-theme="light"] .gantt-frame .popup-wrapper{
  /* The vendor supplies its own 0 10px 24px -3px rgba(0,0,0,.2) here. Dropping the --lift-high
     that used to override it handed the shadow straight back, so it takes an explicit none. */
  background:#ffffff;border:1px solid var(--line2);box-shadow:none;
}
/* The legend swatches were the bright dark-theme hues, which go pale and unreadable as small
   blocks on white. They match the bar strokes instead, so the key and the chart agree. */
:root[data-theme="light"] .gantt-legend .key.bar-critical{background:#b02a48}
:root[data-theme="light"] .gantt-legend .key.bar-active{background:#8639c8}
:root[data-theme="light"] .gantt-legend .key.bar-pending{background:#a698bd}
:root[data-theme="light"] .gantt-legend .key.bar-blocked{background:#8a5510}

/* The Sidekick composer. */
:root[data-theme="light"] .chat-composer form{
  background:#ffffff;border-color:rgba(109,47,190,.28);box-shadow:none;
}

/* These four are already inverted in the dark theme - pale fills carrying dark text - so the
   blanket direction is wrong for them. They darken while everything else lightens, and their
   labels flip the other way. Getting this backwards preserves the contrast ratio and destroys
   the meaning, which is why it would pass an automated check. */
:root[data-theme="light"] .rt-bar,
:root[data-theme="light"] .rt-ghost{background:#6d2fbe;color:#fdfaff;border-color:#5a2aa0}
/* A calendar chip carries its meaning in its colour, and one blanket purple for all four tones
   took that away: light mode drew a monotone month while the legend below it still promised four
   colours. Each tone keeps its meaning and changes its weight - the dark theme's pale fills with
   dark ink do not survive a white page, so these are saturated fills with white ink instead, every
   one of them past 4.5:1. The legend is repainted in the same breath, or it would go on describing
   a calendar nobody is looking at. */
:root[data-theme="light"] .cal-item{color:#ffffff;border-color:transparent}
:root[data-theme="light"] .cal-item.tone-normal,
:root[data-theme="light"] .cal-key.tone-normal{background:#6d2fbe}
:root[data-theme="light"] .cal-item.tone-critical,
:root[data-theme="light"] .cal-key.tone-critical{background:#b3174f}
:root[data-theme="light"] .cal-item.tone-late,
:root[data-theme="light"] .cal-key.tone-late{background:#a5113c;box-shadow:inset 0 0 0 1px #6d0725}
:root[data-theme="light"] .cal-item.tone-done,
:root[data-theme="light"] .cal-key.tone-done{background:#116b4f;opacity:1}
:root[data-theme="light"] .timeline-bar{background:linear-gradient(90deg,#7434bb,#9a5ed6);border-color:#6d2fbe;color:#fff}

/* Surfaces the light theme had recoloured its text on and left its background alone, which is the
   worst of both: dark-theme ink on a dark-theme fill in the middle of a white page. Found by
   walking every element on every route in light mode and flagging anything still painted dark,
   rather than by reading the stylesheet - .minute-chip only exists once a meeting has been
   summarised, and no list of selectors anybody writes from memory contains it. */
:root[data-theme="light"] .minute-chip{background:#f4eefb;border-color:rgba(109,47,190,.18)}
:root[data-theme="light"] .bulk-bar{background:#ffffff;border-color:var(--line);box-shadow:0 12px 30px rgba(58,28,92,.14)}
:root[data-theme="light"] .cal-cell.today{background:#f3ebfd}
:root[data-theme="light"] .notice.danger{background:#fdeef1;border-color:rgba(163,17,60,.22);color:#6d0725}
:root[data-theme="light"] .toast.error{background:#fdeef1;border-color:rgba(163,17,60,.28);color:#6d0725}
:root[data-theme="light"] span.timeline-mark{background:#f0e8fa;border-color:rgba(109,47,190,.2);color:#502e6c}
/* The resource timeline is a whole surface rather than one component, and every part of it was
   still dark: the frame, the day columns, each person's row and the gaps between them. */
:root[data-theme="light"] .rt-scroll{background:#ffffff}
:root[data-theme="light"] .rt-head{background:#faf8fd;border-color:var(--line)}
:root[data-theme="light"] .rt-person{background:#ffffff;border-color:var(--line)}
:root[data-theme="light"] .rt-nobody{background:#f4eefb;color:#502e6c}
:root[data-theme="light"] .rt-cell.weekend,
:root[data-theme="light"] .rt-day.weekend{background:#f6f3fb}
:root[data-theme="light"] .rt-day.today{background:#f3ebfd}
:root[data-theme="light"] .rt-pip{background:rgba(255,255,255,.92);border-color:var(--line)}

/* Four declarations in styles.css carry !important, so a light override has to as well or the
   drag ghost and one load figure stay dark against everything around them. */
:root[data-theme="light"] .drag-ghost{box-shadow:none!important;border-color:#8639c8!important}
:root[data-theme="light"] .drop-before{box-shadow:0 -3px 0 #7434bb!important}

/* ------------------------------------------------------- 11.5 Everything else, and the ink */
:root[data-theme="light"] .avatar{background:#efe6fa;color:#4a2a72;border-color:rgba(109,47,190,.28)}
:root[data-theme="light"] .metric-icon{background:rgba(134,57,200,.1);color:var(--accent2)}
:root[data-theme="light"] .notice{background:#f6f1fc;border-color:rgba(109,47,190,.16);color:#4d4260}
:root[data-theme="light"] .work-card{background:#ffffff;border-color:var(--line);box-shadow:none}
:root[data-theme="light"] .lane{background:#f6f3fb;border-color:var(--line)}
:root[data-theme="light"] .sidebar-profile{background:#f8f5fd;border-color:var(--line)}
:root[data-theme="light"] ::selection{background:rgba(134,57,200,.22);color:#1b1327}
:root[data-theme="light"] ::-webkit-scrollbar-thumb{background-color:rgba(58,28,92,.22)}
:root[data-theme="light"] ::-webkit-scrollbar-thumb:hover{background-color:rgba(58,28,92,.38)}
:root[data-theme="light"] .attention-row.is-late .task-title{color:#a8264a}
:root[data-theme="light"] .metric.is-link:hover{border-color:rgba(109,47,190,.3);background:#fbf9fe}
:root[data-theme="light"] .flag-banner{background:rgba(138,85,16,.07);border-color:rgba(138,85,16,.22)}
:root[data-theme="light"] .avatar-stack .avatar{box-shadow:0 0 0 2px var(--panel)}
:root[data-theme="light"] .work-card .avatar-stack .avatar{box-shadow:0 0 0 2px #ffffff}
:root[data-theme="light"] .avatar-stack .avatar.more{background:rgba(42,18,64,.07);color:#6f6482}

/* .metric .eyebrow hardcodes #b8a6c8 - a pale lilac that reads at 9.3:1 on the dark panel and
   collapses to 2.25:1 on a white one. It is 11px normal-weight text, so 4.5:1 is the bar, not
   the 3:1 that large text gets. Measured at 6.1:1 after this. */
:root[data-theme="light"] .metric .eyebrow{color:#5d5170}
/* The callout eyebrow inside the Sidekick rail is the same problem at 8px. */
:root[data-theme="light"] .sidekick-callout .eyebrow{color:#6d2fbe}
/* A flag is somebody's sentence, so it is set as one - quoted, attributed, and not truncated. */
:root[data-theme="light"] .attention-reason{color:#4a3560;background:rgba(134,57,200,.07)}

/* The week strip on My Day. Its cells are dark tiles with pale text - the tile is the surface,
   so recolouring the text alone leaves white-on-white. Both move. */
:root[data-theme="light"] .day-cell{background:#ffffff;border-color:var(--line)}
:root[data-theme="light"] .day-cell strong{color:var(--text)}
:root[data-theme="light"] .day-cell.active{background:#6d2fbe;border-color:#5a2aa0}
:root[data-theme="light"] .day-cell.active span,
:root[data-theme="light"] .day-cell.active strong{color:#fdfaff}
:root[data-theme="light"] .day-cell i{background:#fdfaff}

/* Light-mode text colours, derived from the dark theme's own hierarchy.
   Sixty-odd colour declarations in styles.css are pale text chosen for a dark panel. Against the
   light ground they land between 1.2:1 and 4.1:1, so every one of them has to move - but what
   they move TO is the whole question, and the first version of this section got it wrong by
   answering "to 4.9:1" for all of them.

   Each dark colour is measured against the panel it was chosen for (#1a1426). That spread runs
   from 4.4:1 for a placeholder to 13:1 for a work item's title, and it is the type hierarchy:
   the designer's answer to what you read first. Each light colour is then solved for the SAME
   ratio against this theme's ground (#f4f1f9), keeping the hue, and keeping the HSL saturation -
   which is normalised by how much room the lightness leaves, so a tint at L=89% comes back as
   the same strength of tint at L=13% rather than as grey. Scaling the channels toward black, as
   the first pass did, is what turned a lilac into mud.

   Saturation is capped at 1.1x the brand accent's own, because a pale colour can sit at S=1 and
   still look gentle - L=.85 leaves it nowhere to go - while the same S reflected to L=.27 comes
   back electric. The floor is 4.6:1 rather than 4.5:1 so a browser rounding a channel
   differently cannot drop one under the bar.

   Measured after: the spread is 4.61:1 to 13.04:1, and nothing sits below 4.5:1.
   Excluded: everything hand-tuned above, and anything on its own coloured ground - toasts,
   timeline bars, the active day cell - which need the opposite treatment, not a darker text. */
:root[data-theme="light"] .badge{color:#46365b}
:root[data-theme="light"] .badge.purple{color:#54218d}
:root[data-theme="light"] .nav-link .icon{color:#756485}
:root[data-theme="light"] .nav-count{color:#6b2396}
:root[data-theme="light"] .crumb strong{color:#524066}
:root[data-theme="light"] .hero h2 em{color:#6d2291}
:root[data-theme="light"] .hero p{color:#5c4370}
:root[data-theme="light"] .task-row .task-title{color:#332144}
/* A nested row carries its own ink, so it needs its own light-mode colour: the dark theme's
   #cdbfd9 is unreadable on a white panel. */
:root[data-theme="light"] .task-children .task-title{color:#453058}
:root[data-theme="light"] .task-children{border-left-color:rgba(109,47,190,.22)}
:root[data-theme="light"] .pillar-symbol{color:#5a238f}
:root[data-theme="light"] .suggestion{color:#57426a}
:root[data-theme="light"] .suggestion .icon{color:#8157a1}
:root[data-theme="light"] .tab.active{color:#551b72}
:root[data-theme="light"] .tabs.underline .tab.active{color:#621f84}
:root[data-theme="light"] .view-toggle .btn.active{color:#622088}
:root[data-theme="light"] .project-icon{color:#6d2291}
:root[data-theme="light"] .workflow-step.current span{color:#581c7a}
:root[data-theme="light"] .work-card .card-title{color:#352047}
/* The nested sub-items carry their own ink, so they need their own light-mode colours: the dark
   theme's #cdbfd9 measures 1.74:1 on a white card. */
:root[data-theme="light"] .work-card .card-sub-title{color:#3d2a4f}
:root[data-theme="light"] .work-card .card-sub.done .card-sub-title{color:#6f6482}
:root[data-theme="light"] .work-card .card-subs{border-left-color:rgba(109,47,190,.22)}
:root[data-theme="light"] .work-card .card-sub-more{color:#632c88}
:root[data-theme="light"] .work-card .dependencies{color:#593815}
:root[data-theme="light"] .dropzone .upload-icon{color:#6d2398}
:root[data-theme="light"] .file-preview>.icon{color:#733f90}
:root[data-theme="light"] .prompt-grid button{color:#4b315e}
:root[data-theme="light"] .prompt-grid .icon{color:#7226a5}
/* The Sidekick's reply kept the dark palette's near-black bubble in light mode: only the text
   colours below were overridden, so dark purple body text sat on #1c1427 at 1.29:1. That is not
   low contrast, it is invisible - and the bubble read as a dark island on a white page. A surface
   has to come from the palette too, not just the ink on it. .user is tinted rather than white so
   the two speakers still differ, the way they do in the dark theme. */
:root[data-theme="light"] .message{background:var(--panel);border-color:var(--line)}
:root[data-theme="light"] .message.user{background:#f2ecfa;border-color:rgba(109,47,190,.18)}
:root[data-theme="light"] .message-sources button{background:#f4eefb;border-color:rgba(109,47,190,.18)}
:root[data-theme="light"] .message-label{color:#6d3a91}
:root[data-theme="light"] .message-body{color:#382546}
:root[data-theme="light"] .message-sources button{color:#622290}
:root[data-theme="light"] .empty>.icon{color:#8255a5}
:root[data-theme="light"] .field>span{color:#543d66}
:root[data-theme="light"] .comment p{color:#44334f}
:root[data-theme="light"] .form-error{color:#69182d}
:root[data-theme="light"] .disclosure summary{color:#632c88}
:root[data-theme="light"] .palette-item{color:#402754}
:root[data-theme="light"] .palette-item .icon{color:#7e44a7}
:root[data-theme="light"] .plain-document{color:#432d55}
:root[data-theme="light"] .auth-hero h1 span{color:#6f218f}
:root[data-theme="light"] .auth-foot{color:#7e6390}
:root[data-theme="light"] .notice.danger{color:#69182b}
:root[data-theme="light"] .voice-reply[aria-pressed=true]{color:#521a6f}
:root[data-theme="light"] .voice-reply[aria-pressed=true] .icon{color:#6a2290}
:root[data-theme="light"] .msg-speak{color:#756485}
:root[data-theme="light"] .msg-speak:hover{color:#6a2290}
:root[data-theme="light"] .msg-speak.speaking{color:#6a2290}
:root[data-theme="light"] .voice-status{color:#7028a6}
:root[data-theme="light"] .btn.text:hover,.btn.text:focus-visible{color:#3e1d7a}
:root[data-theme="light"] .live-transcript{color:#382546}
:root[data-theme="light"] .minute-summary{color:#382546}
:root[data-theme="light"] .record-consent svg{color:#7028a6}
:root[data-theme="light"] .record-consent strong{color:#502e6c}
:root[data-theme="light"] .minute-chip{color:#502e6c}
:root[data-theme="light"] .minute-chip svg{color:#7028a6}
:root[data-theme="light"] .minute-head svg{color:#7028a6}
:root[data-theme="light"] .minute-pending svg{color:#7028a6}
:root[data-theme="light"] .rt-day strong{color:#3d2656}
:root[data-theme="light"] .rt-marker{color:#472f10}
:root[data-theme="light"] .rt-corner small svg{color:#472f10}
:root[data-theme="light"] .rt-person-load{color:#756686}
:root[data-theme="light"] .rt-nobody{color:#604d74}
:root[data-theme="light"] .view-chip.active>button{color:#551b72}
:root[data-theme="light"] .dialog-title-input::placeholder{color:#76668a}
:root[data-theme="light"] .attention-reason{color:#4a3560}
:root[data-theme="light"] .day-cell span{color:#5d5170}

/* Sticky notes in light mode. The washes are mixed for a dark panel; on white they disappear, and
   a note that looks like the page around it stops reading as a note at all. So the same hue is
   mixed against white instead of against nothing, at the strength paper actually has. The ink is
   one dark value for all five - far enough clear of every tint to keep its contrast on each. */
:root[data-theme="light"] .sticky{background:color-mix(in srgb,var(--note-hue) 26%,#fff);
  border-color:color-mix(in srgb,var(--note-hue) 62%,#fff)}
:root[data-theme="light"] .sticky-body{color:#312742}
:root[data-theme="light"] .sticky.done{background:#f2eef7;border-color:#ddd2e8}
:root[data-theme="light"] .sticky.done .sticky-body{color:#5f5570}
/* These hues were mixed to sit on a dark panel, so at 45% on white the unchosen ones all but
   vanish. Lifted here, and the chosen one lifted with them or this rule would dim it too. */
:root[data-theme="light"] .sticky-swatch i{opacity:.62}
:root[data-theme="light"] .sticky-swatch input:checked+i{opacity:1}
