/* ============================================================================
   THE STUDIO | TV MODE
   Ported from dev.darien.games (BUILD v4.0 SN 027). The fit engine and the
   canvas geometry are unchanged. What changed: the ground is ink instead of the
   Dojo's dark, the type is the riso stack, and colour is carried by the day's
   FAMILY rather than by nine near-identical badge tints.
   ========================================================================= */

/* ---- GREY, MEASURED ------------------------------------------------------
   Every grey here used to be color-mix(paper N%, transparent). That mixes the
   COLOUR toward transparent's channels, which in sRGB are black, so it darkens
   and fades at the same time: measured against ink, those greys came out at
   1.06:1 to 1.08:1. Invisible, not subtle.

   Mixing toward the GROUND instead gives a real, predictable grey. Measured on
   ink #101319: 35% paper = 3.00:1, the floor for display type. 48% = 4.50:1,
   the floor for body. 63% = 7.00:1. Nothing small sits below 58% here, and
   nothing on the screen is below 3:1. Re-run contrast.py after touching these.
   ------------------------------------------------------------------------ */

:root{
  --ink:#101319;
  --paper:#F1F0EA;
  --pink:#FF5470;
  --blue:#1F35B5;
  --accent:#B01838;

  /* The four families, measured against ink. Every one clears 4.5:1 and every
     pair is at least dE 31 apart, which is what holds them apart at fifty feet.
     Do not swap these for the calendar's badge colours: SHIP and DUE are the
     same hex there, and so are CRIT and EXAM. */
  --fam-work:#F1F0EA;    /* STUDIO. 70% of the year, so it is the quiet one.  16.29:1 */
  --fam-teach:#667AE8;   /* BRIEF, DEMO                                        4.88:1 */
  --fam-stakes:#FF5470;  /* CRIT, SHIP, DUE, EXAM                              5.98:1 */
  --fam-quiet:#9A9A93;   /* FLEX, OFF                                          6.57:1 */
}

html.tvon, html.tvon body{ overflow:hidden; }
html.tvon body > *:not(#tvstage){ display:none !important; }

#tvstage{
  position:fixed; inset:0; z-index:9999;
  background:var(--ink);
  color:var(--paper);
  overflow:hidden;
}
#tvstage:focus{ outline:none; }

#tvcanvas{
  position:absolute; top:50%; left:50%;
  width:1600px; height:900px;              /* logical canvas. 900 is arbitrary and proven. */
  will-change:transform;
  transform:translate(-50%,-50%) scale(var(--tvs,1));
  transform-origin:center center;
  display:grid; grid-template-rows:auto minmax(0,1fr); gap:26px;
}

/* ---- the header bar ---------------------------------------------------- */
#tvbar{
  display:flex; align-items:baseline; gap:26px;
  font-family:"DM Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  text-transform:uppercase; letter-spacing:.10em;
  border-bottom:3px solid color-mix(in srgb, var(--paper) 32%, var(--ink));
  padding-bottom:14px;
  font-size:calc(24px * var(--tvh,1));
  min-width:0;
}
#tvbar .tvwk{
  color:color-mix(in srgb, var(--paper) 62%, var(--ink));
  font-weight:500; white-space:nowrap; flex:0 0 auto;
}
/* The job is the frame for everything else up here, so it gets the weight. */
#tvbar .tvunit{
  color:var(--paper);
  font-weight:500;
  font-size:1.45em;
  letter-spacing:.06em;
  white-space:nowrap; overflow:hidden; min-width:0; flex:1 1 auto;
}

/* ---- the two columns --------------------------------------------------- */
#tvcanvas .dlog{
  display:grid;
  grid-template-columns:minmax(0,0.92fr) 3px minmax(0,1.28fr);
  gap:0 44px;
  min-height:0; overflow:hidden;
}
#tvcanvas .ocut{
  background:color-mix(in srgb, var(--paper) 28%, var(--ink));
  align-self:stretch;
}
#tvcanvas .arrows,
#tvcanvas .ofoot{ display:none !important; }   /* nobody clicks a television */

/* LEFT: the stamp. Low variance, so --tvf lands in a narrow band. */
#tvcanvas .ostamp{
  --f:var(--tvf,1);
  display:flex; flex-direction:column; gap:calc(18px * var(--f));
  min-width:0; min-height:0; overflow:hidden;
  /* Keep more than the probe's 10px of clearance on the right, or every
     full-width block child registers as clipped and lands in the liar list. */
  padding-right:calc(30px * var(--f));
}
#tvcanvas .dateline{
  position:relative;
  display:flex; align-items:baseline; justify-content:space-between; gap:calc(20px * var(--f));
  min-width:0;
}
#tvcanvas .bigdate{
  position:relative;
  display:flex; align-items:baseline; gap:calc(18px * var(--f));
  font-family:Fraunces,"Newsreader",Georgia,serif;
  min-width:0;
}
#tvcanvas .bigdate .dt{
  font-weight:700;                           /* 900 is a poster. This is an editorial page. */
  font-size:calc(112px * var(--f));
  line-height:1;
  letter-spacing:-.015em;
  color:var(--paper);
  white-space:nowrap;
}
/* The weekday is sized off the PANEL, never off the date. In em of the date it
   changes size whenever a longer date string trips a smaller class, and then
   TUESDAY under 9/8 comes out bigger than SATURDAY under 9/12. */
#tvcanvas .bigdate .wd{
  font-family:"DM Mono",ui-monospace,monospace;
  font-weight:400;
  font-size:calc(26px * var(--f) * var(--wds,1));
  letter-spacing:.22em; white-space:nowrap;
  color:color-mix(in srgb, var(--paper) 68%, var(--ink));
}

/* The day type. This is the payload, not a label: CRIT day and STUDIO day are
   two different rooms and the back row has to read which one from the doorway. */
#tvcanvas .otagline{ min-width:0; }
/* The type is a ruled label, not a filled slab. Colour still does the work of
   saying which room this is, but it does it through a heavy rule and the word
   itself, the way the rest of the site handles emphasis. A solid block of pink
   under a 300px date is a poster, and this is not a poster.
   --tgs is set by fitTag(). The main fit cannot police this box: its
   half-a-line tolerance runs to tens of pixels sideways at display sizes, so a
   whole missing letter reads as "fits". STUDIO is the longest word. */
#tvcanvas .otag{
  display:inline-block;
  font-family:"DM Mono",ui-monospace,monospace;
  font-weight:500;
  font-size:calc(40px * var(--f) * var(--tgs,1));
  line-height:1.1;
  letter-spacing:.20em;
  padding-bottom:calc(12px * var(--f) * var(--tgs,1));
  border-bottom:calc(6px * var(--f)) solid var(--fam,var(--fam-work));
  color:var(--fam,var(--fam-work));
  white-space:nowrap;
}
/* FLEX and OFF share a colour on purpose. The calendar already separates them
   with a dashed border, so the TV keeps that second axis rather than inventing
   a tenth hue nobody can name. */
#tvcanvas .dlog[data-type="OFF"] .otag{ border-bottom-style:dashed; }

/* The hierarchy was date -> body with nothing between, which is what made the
   jump read wrong. The job name is the middle step. */
#tvcanvas .osub{
  font-family:"Newsreader",Georgia,serif;
  font-style:italic;
  font-size:calc(34px * var(--f));
  line-height:1.3;
  color:color-mix(in srgb, var(--paper) 80%, var(--ink));
  max-width:92%;
  text-wrap:balance;
}
/* THE PRO-TIP. The Dojo's shape, in the riso palette: a corner cut and a hard
   offset shadow, which is a second ink layer printed out of register. No tilt.
   Two reasons. A rotated box reads as a sticky note, and riso's whole language
   is a grid printed slightly off itself, not objects stuck on top of one. And a
   transform inflates the parent's scroll dimensions, which is trap 1 and trap 2
   in the handoff: the shine broke this exact fit twice that way. */
#tvcanvas .otip{
  position:relative;
  align-self:flex-start;
  max-width:96%;
  margin:calc(6px * var(--f)) 0;
  padding:calc(16px * var(--f)) calc(20px * var(--f));
  background:var(--paper);
  color:var(--ink);
  font-family:"Newsreader",Georgia,serif;
  font-size:calc(26px * var(--f));
  line-height:1.34;
  clip-path:polygon(calc(14px * var(--f)) 0, 100% 0, 100% calc(100% - 14px * var(--f)),
                    calc(100% - 14px * var(--f)) 100%, 0 100%, 0 calc(14px * var(--f)));
  box-shadow:calc(10px * var(--f)) calc(10px * var(--f)) 0 var(--fam,var(--fam-work));
}
#tvcanvas .otip .tl{
  display:flex; align-items:center; gap:.5em;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(18px * var(--f));
  letter-spacing:.18em;
  color:var(--accent);
  margin-bottom:calc(9px * var(--f));
}
#tvcanvas .otip .tl .ti{
  flex:none; width:1.7em; height:1.7em; display:block;
  fill:currentColor; shape-rendering:crispEdges;
}
#tvcanvas .otip b, #tvcanvas .otip strong{ color:var(--blue); }

/* Permission, not a pep talk. Persona puts TAKE YOUR TIME on a calendar full of
   deadlines for a reason: the countdown is already doing the pushing, and the
   screen needs one line that is not pushing. Sized small, set in the day's
   colour, and never exclamatory. */
#tvcanvas .osay{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(30px * var(--f));
  font-weight:500;
  letter-spacing:.24em;
  color:color-mix(in srgb, var(--paper) 78%, var(--ink));
  padding-top:calc(14px * var(--f));
  border-top:calc(3px * var(--f)) solid color-mix(in srgb, var(--paper) 34%, var(--ink));
  align-self:flex-start;
}

#tvcanvas .oarc{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(24px * var(--f));
  letter-spacing:.10em;
  color:color-mix(in srgb, var(--paper) 60%, var(--ink));
  margin-bottom:auto;
}
#tvcanvas .oarc .n{
  font-size:calc(40px * var(--f));
  color:var(--paper);
}


/* RIGHT: the job card. High variance, so this is the column the search works for. */
#tvcanvas .olog{
  --f:var(--tvl,1);
  display:flex; flex-direction:column; gap:calc(20px * var(--f));
  min-width:0; min-height:0; overflow:hidden;
  padding-right:calc(30px * var(--f));   /* same reason as .ostamp above */
}
#tvcanvas .olog h3{
  margin:0;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(34px * var(--f));
  font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  color:color-mix(in srgb, var(--paper) 66%, var(--ink));
}
#tvcanvas .obar{
  position:relative; overflow:hidden;
  display:grid; grid-template-columns:minmax(0,calc(215px * var(--f))) minmax(0,1fr);
  gap:calc(22px * var(--f));
  padding:calc(26px * var(--f)) 0;
  border-top:2px solid color-mix(in srgb, var(--paper) 32%, var(--ink));
  min-width:0;
}
#tvcanvas .obar .k{
  align-self:baseline;
  justify-self:start;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(28px * var(--f));
  font-weight:600; letter-spacing:.10em;
  padding:calc(9px * var(--f)) calc(15px * var(--f));
  background:color-mix(in srgb, var(--paper) 24%, var(--ink));
  color:color-mix(in srgb, var(--paper) 78%, var(--ink));
  line-height:1.2;
}
#tvcanvas .obar .v{
  font-family:"Newsreader",Georgia,serif;
  font-size:calc(32px * var(--f));
  line-height:1.35;
  color:var(--paper);
  min-width:0;
}
/* still-open work: a list, not a sentence */
#tvcanvas .owl{ margin:0; padding:0; list-style:none; }
#tvcanvas .owl li{
  display:grid; grid-template-columns:calc(178px * var(--f)) minmax(0,1fr);
  gap:calc(16px * var(--f));
  padding-bottom:calc(14px * var(--f));
  font-size:calc(30px * var(--f));
  line-height:1.25;
}
/* The deadline leads. A checkbox on a wall is a control nobody can operate, and
   an unchecked box reads "nothing is done" even to the kid who finished. */
#tvcanvas .owl .wn{
  justify-self:start; align-self:start;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(26px * var(--f));
  font-weight:500; letter-spacing:.06em;
  padding:calc(7px * var(--f)) calc(11px * var(--f));
  border:calc(2px * var(--f)) solid color-mix(in srgb, var(--paper) 42%, var(--ink));
  color:color-mix(in srgb, var(--paper) 68%, var(--ink));
  white-space:nowrap;
}
#tvcanvas .owl li.near .wn{ background:var(--fam-stakes); border-color:var(--fam-stakes); color:var(--ink); }
#tvcanvas .owl li.xc .wn{ background:var(--fam-teach); border-color:var(--fam-teach); color:var(--ink); font-size:calc(30px * var(--f)); }
#tvcanvas .owl .nt{ grid-column:2; }
#tvcanvas .owl li.xc{ color:var(--fam-teach); }
#tvcanvas .owl .nt{
  display:block;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(23px * var(--f));
  letter-spacing:.06em;
  color:color-mix(in srgb, var(--paper) 58%, var(--ink));
}
/* TODAY is the first thing read and the reason the screen is on. */
#tvcanvas .obar.today{ border-top:0; padding-top:0; align-items:baseline; }
#tvcanvas .obar.today .k{ background:var(--paper); color:var(--ink); }
#tvcanvas .obar.today .v{ font-size:calc(50px * var(--f)); line-height:1.24; }

#tvcanvas .obar.due .k{ background:var(--fam-stakes); color:var(--ink); }
#tvcanvas .obar.due .v{ color:var(--fam-stakes); }

/* The rows are what you point at while you talk, so they answer the pointer.
   Nothing moves layout: a hover that shifts a row on a mirrored TV is worse
   than no hover at all. */
@media (pointer:fine){
  #tvcanvas .obar{ cursor:default; transition:background .12s ease; }
  #tvcanvas .obar:hover{ background:color-mix(in srgb, var(--paper) 7%, var(--ink)); }
  #tvcanvas .owl li{ transition:background .12s ease; }
  #tvcanvas .owl li:hover{ background:color-mix(in srgb, var(--paper) 9%, var(--ink)); }
}

/* THE SHINE. Three rules, all learned the expensive way:
   1. animate background-position, never transform: a transform on the sweep
      inflates the parent's scrollWidth and the fit believes it
   2. inset:0, never negative offsets, for the same reason on scrollHeight
   3. fade it out or it parks at the end of the sweep and sits there */
@keyframes labshine{
  0%{background-position:135% 0;opacity:1}
  7%{background-position:-40% 0;opacity:1}
  7.5%{opacity:0}
  100%{background-position:-40% 0;opacity:0}
}
#tvcanvas .obar::after{
  content:""; position:absolute; inset:0; z-index:2; pointer-events:none;
  background:linear-gradient(100deg,transparent 42%,rgba(255,255,255,.16) 50%,transparent 58%);
  background-size:260% 100%; background-position:135% 0; background-repeat:no-repeat;
  opacity:0; animation:labshine 54s linear infinite; animation-delay:var(--shd,0s);
}

/* ---- urgency ------------------------------------------------------------
   Blinking everything teaches the room to ignore blinking. Three days out is
   silent. And a deadline swaps colour rather than fading, because fading to
   32% reads as a rendering fault about half the time. */
@keyframes duepulse{ 0%,49%{ background:var(--fam-stakes); color:var(--ink); } 50%,100%{ background:transparent; color:var(--fam-stakes); } }
#tvcanvas .obar.due.soon .k{ animation:duepulse 1.9s steps(1,end) infinite; }
#tvcanvas .obar.due.now  .k{ animation:duepulse 0.62s steps(1,end) infinite; }
#tvcanvas .owl li.near .wn{ animation:duepulse 1.9s steps(1,end) infinite; }
/* the whole row wears a ring when it is live, so the alarm reads from the corner
   of the eye and not just from the label */
#tvcanvas .obar.due.soon, #tvcanvas .obar.due.now{
  box-shadow:inset 0 0 0 calc(3px * var(--f)) var(--fam-stakes);
  padding-left:calc(16px * var(--f)); padding-right:calc(16px * var(--f));
}

/* ---- the week strip: not on the TV ---------------------------------------
   The Dojo drops it in TV mode on wide screens and it should drop here too. Five
   day names is the lowest-value thing that could occupy a third of the screen. */
/* ---- the week strip (page use, kept for when the panel lands on the page) --- */
#tvcanvas .mini{
  /* Not on the TV. The display:none has to come AFTER the flex declaration that
     used to sit in this same block, or the later one in the block just wins. */
  --f:var(--tvl,1);
  grid-column:1 / -1;
  flex-direction:column; gap:calc(12px * var(--f));
  display:none;
  min-width:0;
  padding-right:calc(30px * var(--f));   /* same reason as .ostamp above */
}
#tvcanvas .mini .ml{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(24px * var(--f));
  letter-spacing:.18em; text-transform:uppercase;
  color:color-mix(in srgb, var(--paper) 62%, var(--ink));
}
#tvcanvas .mdays{ display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:calc(12px * var(--f)); }
#tvcanvas .mdays span{
  display:block; overflow:hidden;
  padding:calc(10px * var(--f)) 0;
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:calc(20px * var(--f));
  letter-spacing:.10em;
  color:color-mix(in srgb, var(--paper) 62%, var(--ink));
  border-top:1px solid color-mix(in srgb, var(--paper) 30%, var(--ink));
  white-space:nowrap; text-overflow:clip;
}
#tvcanvas .mdays span b{ display:block; font-weight:500; color:var(--famcell,inherit); }
#tvcanvas .mdays span.today{
  border-top-width:calc(4px * var(--f));
  border-top-color:var(--fam,var(--fam-work));
  color:var(--paper);
}

/* Very wide and short: stand the strip up beside the card instead of under it.
   Locking the canvas to 16:9 filled 63% of a 3128x1179 window with black bars.
   Following the display instead: 96%. */
html.tvwide #tvcanvas .dlog{ grid-template-columns:minmax(0,0.78fr) 3px minmax(0,1.22fr); }

@media (prefers-reduced-motion:reduce){
  #tvcanvas .obar::after{ animation:none; opacity:0; }
  #tvcanvas .obar.due .v{ animation:none !important; }
}

/* ============================================================================
   ON THE PAGE
   The same node, on paper instead of ink. TV mode borrows it and restyles it,
   which is the whole reason there is only ever one of these.
   Here it can be hovered and stepped, so it gets the affordances a wall cannot.

   EVERY rule below is scoped to html:not(.tvon). Without that the page card's
   paper background and border follow the node into the overlay, and the fit
   engine starts measuring a white box on a black screen: eight liars and a
   collapse to 0.48 on the first ship day I checked.
   ========================================================================= */
html:not(.tvon) #today{ display:block; }                      /* was hidden; the page has it now */

html:not(.tvon) .dlog{
  --f:1;
  position:relative;
  margin:2rem 0;
  padding:1.6rem 1.7rem;
  background:var(--paper);
  color:var(--ink);
  border:2px solid var(--ink);
  box-shadow:8px 8px 0 var(--fam,var(--ink));  /* riso: a second ink layer, out of register */
}
html:not(.tvon) .dlog .ocut{ display:none; }

html:not(.tvon) .dlog .arrows{
  position:absolute; top:1rem; right:1rem;
  display:flex; gap:.4rem;
}
html:not(.tvon) .dlog .arrows button{
  font:inherit; font-size:1.2rem; line-height:1;
  width:2.1rem; height:2.1rem;
  background:transparent; color:var(--ink);
  border:2px solid var(--ink); cursor:pointer;
  transition:background .12s ease, color .12s ease, transform .12s ease;
}
html:not(.tvon) .dlog .arrows button:hover:not(:disabled){ background:var(--ink); color:var(--paper); transform:translateY(-2px); }
html:not(.tvon) .dlog .arrows button:disabled{ opacity:.28; cursor:default; }

html:not(.tvon) .dlog .bigdate{ display:flex; align-items:baseline; gap:.6rem; font-family:Fraunces,Georgia,serif; }
html:not(.tvon) .dlog .bigdate .dt{ font-size:3.2rem; font-weight:700; line-height:1; }
html:not(.tvon) .dlog .bigdate .wd{ font-family:"DM Mono",monospace; font-size:.78rem; letter-spacing:.22em; opacity:.6; }

html:not(.tvon) .dlog .otag{
  display:inline-block; margin:.7rem 0 0;
  font-family:"DM Mono",monospace; font-size:1rem; font-weight:500; letter-spacing:.2em;
  padding-bottom:.3rem;
  border-bottom:4px solid var(--fam,var(--ink));
  color:var(--fam,var(--ink));
}
html:not(.tvon) .dlog[data-type="OFF"] .otag{ border-bottom-style:dashed; }
/* pink on paper is 2.72:1, so on this ground the stakes colour is the darker accent */
html:not(.tvon) .dlog[data-type="CRIT"] .otag, html:not(.tvon) .dlog[data-type="SHIP"] .otag,
html:not(.tvon) .dlog[data-type="DUE"] .otag, html:not(.tvon) .dlog[data-type="EXAM"] .otag{ color:var(--accent); border-bottom-color:var(--accent); }

html:not(.tvon) .dlog .otip{
  margin:1rem 0; padding:.85rem 1rem;
  background:var(--ink); color:var(--paper);
  font-family:"Newsreader",Georgia,serif; font-size:1rem; line-height:1.4;
  clip-path:polygon(10px 0,100% 0,100% calc(100% - 10px),calc(100% - 10px) 100%,0 100%,0 10px);
}
html:not(.tvon) .dlog .otip .tl{
  display:flex; align-items:center; gap:.5em;
  font-family:"DM Mono",monospace; font-size:.68rem; letter-spacing:.18em;
  color:var(--pink); margin-bottom:.45em;
}
html:not(.tvon) .dlog .otip .tl .ti{ flex:none; width:1.7em; height:1.7em; fill:currentColor; shape-rendering:crispEdges; }
html:not(.tvon) .dlog .otip b{ color:var(--pink); }

html:not(.tvon) .dlog .osay{
  font-family:"DM Mono",monospace; font-size:.8rem; letter-spacing:.24em;
  padding-top:.5rem; border-top:3px solid var(--fam,var(--ink));
  display:inline-block; margin:.6rem 0;
}
html:not(.tvon) .dlog .oarc{ font-family:"DM Mono",monospace; font-size:.82rem; letter-spacing:.06em; opacity:.65; }
html:not(.tvon) .dlog .oarc .n{ font-size:1.3rem; opacity:1; }

html:not(.tvon) .dlog .obar{
  display:grid; grid-template-columns:7rem minmax(0,1fr); gap:1rem;
  padding:.9rem 0; border-top:1px solid rgba(16,19,25,.18);
}
html:not(.tvon) .dlog .obar .k{ font-family:"DM Mono",monospace; font-size:.72rem; letter-spacing:.18em; opacity:.55; padding-top:.3rem; }
html:not(.tvon) .dlog .obar .v{ font-family:"Newsreader",Georgia,serif; font-size:1.06rem; line-height:1.45; }
html:not(.tvon) .dlog .obar.today .v{ font-size:1.2rem; }

/* ---- THE ALARM. Ported from the Dojo: the label is a chip that swaps colours,
   it never fades, and the bar wears a ring so the alarm is visible even from the
   corner of your eye. Silent at three days out, which is the whole point. ---- */
@keyframes duechip{
  0%,49%  { background:var(--accent); color:var(--paper); }
  50%,100%{ background:transparent;   color:var(--accent); }
}
html:not(.tvon) .dlog .obar.due .k{ color:var(--accent); opacity:1; }
html:not(.tvon) .dlog .obar.due.soon,
html:not(.tvon) .dlog .obar.due.now{
  border-top-color:var(--accent);
  box-shadow:inset 0 0 0 2px var(--accent);
  padding-left:.7rem; padding-right:.7rem;
}
html:not(.tvon) .dlog .obar.due.soon .k{ animation:duechip 1.9s steps(1,end) infinite; }
html:not(.tvon) .dlog .obar.due.now  .k{ animation:duechip .62s steps(1,end) infinite; }

html:not(.tvon) .dlog .owl{ margin:0; padding:0; list-style:none; }
html:not(.tvon) .dlog .owl li{
  display:grid; grid-template-columns:6rem minmax(0,1fr); gap:.8rem;
  padding:.45rem .5rem; margin-left:-.5rem;
  transition:background .12s ease;
}
html:not(.tvon) .dlog .owl li:hover{ background:rgba(16,19,25,.06); }
html:not(.tvon) .dlog .owl .wn{ font-family:"DM Mono",monospace; font-size:.72rem; letter-spacing:.06em; opacity:.55; padding-top:.28rem; }
html:not(.tvon) .dlog .owl .nt{ grid-column:2; font-family:"DM Mono",monospace; font-size:.68rem; opacity:.5; }
html:not(.tvon) .dlog .owl li.near{ color:var(--accent); }
html:not(.tvon) .dlog .owl li.near .wn{ color:var(--accent); opacity:1; animation:duechip 1.9s steps(1,end) infinite; padding:.28rem .35rem 0; }
html:not(.tvon) .dlog .owl li.xc{ color:var(--blue); }
html:not(.tvon) .dlog .owl li.xc .wn{ color:var(--blue); opacity:1; font-size:1rem; }

html:not(.tvon) .dlog .mini{ margin-top:1.2rem; }
html:not(.tvon) .dlog .mini .ml{ font-family:"DM Mono",monospace; font-size:.7rem; letter-spacing:.18em; opacity:.5; }
html:not(.tvon) .dlog .mdays{ display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:.5rem; margin-top:.5rem; }
html:not(.tvon) .dlog .mdays span{
  display:block; padding:.5rem .4rem; cursor:pointer;
  font-family:"DM Mono",monospace; font-size:.72rem; letter-spacing:.06em;
  border-top:2px solid rgba(16,19,25,.2);
  transition:border-top-color .12s ease, background .12s ease, transform .12s ease;
}
html:not(.tvon) .dlog .mdays span:hover{ background:rgba(16,19,25,.07); transform:translateY(-2px); border-top-color:var(--ink); }
html:not(.tvon) .dlog .mdays span b{ display:block; font-weight:500; }
html:not(.tvon) .dlog .mdays span.today{ border-top-width:4px; border-top-color:var(--fam,var(--ink)); }

html:not(.tvon) .dlog .ofoot{ margin-top:1rem; font-family:"DM Mono",monospace; font-size:.75rem; letter-spacing:.12em; }
html:not(.tvon) .dlog .ofoot a{ color:var(--ink); text-decoration:underline; text-underline-offset:3px; }
#tvcanvas .ofoot a{ color:var(--paper); }   /* hidden on the TV, but never left at browser-default blue */
html:not(.tvon) .dlog .ofoot a:hover{ background:var(--ink); color:var(--paper); text-decoration:none; }

@media (prefers-reduced-motion:reduce){
html:not(.tvon) .dlog .obar.due .k, html:not(.tvon) .dlog .owl li.near .wn{ animation:none; }
}

/* ============================================================================
   WEATHER
   Open-Meteo, no key. One colour, drawn in the day's family colour so it reads
   as part of the same system rather than a widget someone bolted on.
   Idle motion runs on TWOS (steps(2)), not smoothly: a low frame rate reads as
   deliberate, and a smooth tween reads as a web page.
   ========================================================================= */
#tvcanvas .wxwrap{
  flex:none;
  align-self:baseline;          /* sits on the same line as 9/18 and FRIDAY */
  color:var(--fam,var(--fam-work));
}
#tvcanvas .wxplate{ display:block; }
#tvcanvas .wx{
  display:block;
  width:calc(56px * var(--tvf,1));
  height:auto;
}
#tvcanvas .wx .wxin{ fill:currentColor; }

@keyframes wxbob   { 0%,100%{ transform:translateY(0) }   50%{ transform:translateY(-4%) } }
@keyframes wxdrift { 0%,100%{ transform:translateX(-2%) } 50%{ transform:translateX(2%) } }

/* No opacity animation anywhere. A thing on a wall that changes brightness
   reads as a fault or an alarm, and this is neither: it is the weather. Motion
   only, small, slow, still on twos. Storm is the one exception and it moves
   rather than blinks. */
#tvcanvas .wx-moon  .wxin,
#tvcanvas .wx-cloud .wxin,
#tvcanvas .wx-partly .wxin,
#tvcanvas .wx-partlynight .wxin,
#tvcanvas .wx-fog   .wxin{ animation:wxdrift 6.4s steps(2,end) infinite; transform-origin:50% 50%; }
#tvcanvas .wx-rain  .wxin,
#tvcanvas .wx-sleet .wxin,
#tvcanvas .wx-snow  .wxin{ animation:wxbob 2.2s steps(2,end) infinite; transform-origin:50% 50%; }
#tvcanvas .wx-storm .wxin{ animation:wxbob 1.4s steps(2,end) infinite; transform-origin:50% 50%; }
#tvcanvas .wx-sun   .wxin{ animation:none; }        /* the sun just sits there */

/* On the page, same icon, ink on paper, no motion: it is decoration there. */
html:not(.tvon) .dlog .wxwrap{ float:right; color:var(--ink); margin-left:1rem; }
html:not(.tvon) .dlog .wxplate{ display:block; }
html:not(.tvon) .dlog .wx{ display:block; width:1.8rem; height:auto; }
html:not(.tvon) .dlog .wx .wxin{ fill:currentColor; }

@media (prefers-reduced-motion:reduce){
  #tvcanvas .wx .wxin{ animation:none !important; }
}
