/*
Theme Name: Lobby Screens
Description: TV display for residential building lobbies (35-45"). Static presentation, no user interaction — server-rendered live data (Ynet, ONE, weather, Shabbat times) refreshed on each reload.
Version: 8.1

v7 — "Otzma Signage". Aviv rewrote the brief: the screen is an explicit
four-card dashboard (Ynet · ONE · Shabbat · resident notices) over a
full-width Ynet ticker. This reverses the v5 brief's "לא Dashboard — אין רשת
של כרטיסים קטנים"; the reversal is deliberate and Aviv's call, recorded here
so the older note in the README isn't read as still binding.

v7.1 is built against the client mockup (mockups/v7-client-mockup.png), which
turned up after the first pass and corrected two structural mistakes made from
the written brief alone:
  - the logo sits on the RIGHT and the clock on the LEFT. §3 of the brief says
    the opposite in words, but the mockup is RTL-correct and wins.
  - the cards run, right to left: notices · Shabbat · ONE · Ynet. The first
    pass had that order mirrored.
The surface treatment moved with it: near-opaque black panels with a thin gold
rule, not the translucent glass of v5/v6. The background photo is only really
present behind the header, exactly as the mockup stages it.

v7.2 acts on Aviv's sketch (mockups/v7-2-layout-sketch.png): the Ynet card is
gone — the ticker already carries that feed, so a card of it was the same
source twice — and the three that remain move to two side columns with the
middle left open. The background clip is the reason for the change, so the
grading was reopened to match: it is now a foreground element in its own strip
rather than texture under four panels.

v8 replaced the background clip with material built from the client's own brand
(charcoal, champagne gold, their printed contour field), which let the cards
open up to real translucency.

v8.1 fixes what v8 left behind in the header. The header was two groups pinned
to the two edges with ~990px of dead space between them — correct in v7.2, when
a clip played behind it and the gap was the window onto the clip, and simply a
hole once the clip was gone. It is now a rail: five cells across the full width
on two shared baselines, separated by one hairline component. The header scrim
went with it, re-measured rather than assumed.

v8.1.1 — Aviv cut the building-name cell and asked for everything left on the
rail to be bigger. Those are one change, not two: four cells instead of five
free ~260px, which is what pays for the type. The second band goes 19→28px,
which is the one that mattered — at lobby distance 19px was decoration.

What carries over: Heebo only with thin-vs-bold contrast, gold as a measured
accent, and the fixed 1920x1080 canvas scaled to fit any TV (.fit).

All sizes are design-px on the fixed canvas — see the .fit rule. Layout is
composed of independent widget components; every .w-* block below is one
widget's CSS (see template-parts/widgets/).
*/

@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:100;
  font-display:swap;
  src:url('assets/fonts/heebo-100-hebrew.woff2') format('woff2');
  unicode-range:U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:100;
  font-display:swap;
  src:url('assets/fonts/heebo-100-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:300;
  font-display:swap;
  src:url('assets/fonts/heebo-300-hebrew.woff2') format('woff2');
  unicode-range:U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:300;
  font-display:swap;
  src:url('assets/fonts/heebo-300-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url('assets/fonts/heebo-400-hebrew.woff2') format('woff2');
  unicode-range:U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url('assets/fonts/heebo-400-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url('assets/fonts/heebo-600-hebrew.woff2') format('woff2');
  unicode-range:U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url('assets/fonts/heebo-600-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:800;
  font-display:swap;
  src:url('assets/fonts/heebo-800-hebrew.woff2') format('woff2');
  unicode-range:U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face{
  font-family:'Heebo';
  font-style:normal;
  font-weight:800;
  font-display:swap;
  src:url('assets/fonts/heebo-800-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The clock used to have a face of its own — Fraunces, a high-contrast
   display serif. v9.0 took it out: Aviv asked for the clock and the
   temperature to carry exactly the font the Shabbat times do, and uniformity
   across the screen is worth more than one distinctive element on it. The
   .woff2 stays in assets/fonts so putting it back is an @font-face and a
   font-family, but nothing loads it now.
*/

:root{
  --night:#0A0B0E;
  /* v7.1: the mockup's cards are near-solid black panels, not glass. A little
     translucency is kept so the background still reads as depth behind them. */
  /* v8: the panels were pushed to near-opaque only because a bright daylight
     clip sat behind them. With the brand background under control they open
     up to ~0.80/0.86, so the contour field and the gold glow read through the
     cards instead of being blocked by them. Re-measured below. */
  --card-top:rgba(19,21,27,0.80);
  --card-bot:rgba(11,12,16,0.86);
  --line:rgba(255,255,255,0.09);
  --frame:rgba(201,188,156,0.26);      /* the thin gold rule around a card */
  --frame-soft:rgba(255,255,255,0.14);
  --gold:#9D9379;                      /* brand value — rules, diamonds, dots */
  --gold-line:rgba(157,147,121,0.55);
  /* Display gold. The brand value is too dark to set text in against black at
     any useful size; this is the same hue opened up, and it is used only for
     headings and labels, never for body copy. Measured below. */
  --gold-text:#D9C79C;
  --white:#F4F3EF;
  --dim:rgba(244,243,239,0.78);
  --dimmer:rgba(244,243,239,0.60);
  --ink:#14161B;                       /* text on the light Shabbat panel */
  --ink-dim:rgba(20,22,27,0.66);
  /* Heebo ships here at 100 / 300 / 400 / 600 / 800 ONLY (see the @font-face
     block above — the files are local, there is no variable font and no
     network fallback). Asking for any other weight does not fail loudly: the
     browser synthesises it from the nearest face, so headlines quietly render
     at the wrong weight and differently across browsers. v7.2 shipped with
     200 on the clock and 700 on five headline rules before this was caught.
     Use only the five values above. */
  --sans:'Heebo','Arial Hebrew',sans-serif;

  --radius:20px;
  --pad:20px 22px 22px;

  /* ---- type scale (v9.1) ----
     The screen had 15 px sizes with no ratio between them, each picked on its
     own and each defensible on its own. Six is the number of text ROLES this
     screen actually has, so six is the number of sizes it gets. The steps land
     on values already approved wherever one existed rather than being derived
     from a ratio and then imposed — a scale that forces approved type to move
     buys consistency by spending trust. 22 is the floor: below it nothing on a
     lobby TV is read standing up. */
  --t-meta:22px;      /* labels, timestamps, tags, status  */
  --t-body:30px;      /* running copy                      */
  --t-head:34px;      /* list headlines, the ticker        */
  --t-card:44px;      /* a card's own title                */
  --t-display:58px;   /* the one hero line per card        */
  --t-numeral:104px;  /* clock and temperature             */

  /* ---- spacing scale ----
     19 distinct values were in use, 11 of them off any grid (2,3,6,9,10,13,14,
     18,22,30,54). These seven cover every one of them. The layout constants
     that encode a MEASUREMENT — 54px grid padding, the 30px channel, the card
     widths — are not on this scale and should not be: a number that records a
     measurement is not design debt. */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-6:24px; --s-8:32px; --s-12:48px;

  /* ---- line-height: display, headline, body. Nothing else. ---- */
  --lh-display:1; --lh-head:1.25; --lh-body:1.5;

  /* ---- radii: item, media, card ---- */
  --r-item:8px; --r-media:16px; --r-card:28px;

  /* header rail (v8.1). Every cell on the rail is one cell-height tall and
     splits into the same two bands, so the big line of each cell shares a
     baseline with every other and the small lines share a second one. Tokens
     rather than the same four numbers copied into four widget blocks.

     v8.1.1 — the whole scale goes up for lobby distance. This is a screen read
     standing up, from several metres, by someone who will not walk closer: the
     rule of thumb is roughly one inch of cap height per three metres of
     viewing distance, and on a 40" 1080p panel (~55 design-px to the inch) the
     old 19px second lines came to a quarter-inch. They were legible at a desk
     and decorative in a lobby. The second band is the big change (19→28px);
     the first goes up more modestly because 78px already cleared the bar. */
  --hdr-cell-h:150px;
  --hdr-lede-h:96px;
  --hdr-sub-h:42px;
  --hdr-band-gap:12px;
}

/* CONTRAST — card and ticker figures measured for v7.2 (9.9.2026) by
   compositing the CSS layers in maths rather than eyeballing a screenshot.
   Small-text bar is 4.5:1; large display text is 3:1.
     card body   --white     15.1 : 1
     card body   --dim        9.6 : 1
     notice date --dimmer     6.2 : 1
     card label  --gold-text 10.1 : 1
     Shabbat panel --ink on #F2F0EA   14.1 : 1
     ticker text                      17.3 : 1
   These were taken against the background clip, which is gone as of v8; the
   clip was the BRIGHT case, so the brand background can only improve them.

   The header is the only text with no panel under it and is the one place worth
   re-deriving: it was re-measured for v8.1 against the brand background, with
   the v7.2 header scrim removed. Figures and method are in the .zone-top block
   below. Redo that measurement if the background layers change. */

*{box-sizing:border-box;margin:0;}
html,body{height:100%;overflow:hidden;}
body{
  font-family:var(--sans);
  color:var(--white);
  background:#000;      /* letterbox bars when the window isn't 16:9 */
  -webkit-font-smoothing:antialiased;
}
img{display:block;}

/* ---------- fit-to-screen canvas ----------
   Fixed 1920x1080 design canvas scaled as one unit to any window/TV (JS sets
   the transform on resize), so the full frame is always visible and nothing
   ever needs to scroll. Every size below is a design-px on it. */
.fit{
  position:absolute;top:50%;left:50%;
  width:1920px;height:1080px;
  transform:translate(-50%,-50%) scale(1);
  overflow:hidden;
  background:var(--night);
}

/* ---------- ambient background ----------
   v8: the stock clip is gone. The background is now built from the client's
   own material — their charcoal, their champagne gold, and the topographic
   contour field taken off their printed signage — so it reads as an extension
   of the brand rather than as footage borrowed from somewhere.

   Three practical problems this also retires, all of them real and all of them
   recorded in the README: the clip was a third party's work with their
   watermark burned in (a licensing exposure for a paying client), it was a
   640x360 source stretched roughly 3x onto a 1080p screen, and its daylight
   brightness is what forced the cards to be near-opaque in the first place.
   Luminance is fully under our control now, so the cards open up (below). */
.bg-base{
  position:absolute;inset:0;z-index:0;
  background:
    /* two soft champagne glows, placed to light the open centre column and to
       lift the far corner behind the stack */
    radial-gradient(1000px 700px at 50% 42%, rgba(157,147,121,.22), transparent 70%),
    radial-gradient(760px 560px at 12% 88%, rgba(157,147,121,.09), transparent 66%),
    /* the brand charcoal, warm at the top and deepening toward the base */
    linear-gradient(168deg, #242621 0%, #1B1C1A 46%, #0E0F12 100%);
}
/* the client's contour field. Two copies at different scales drifting against
   each other turn a 2400x103 strip into a slow, non-repeating texture; mirrored
   so neither copy reads as the same band twice. */
.bg-contours{
  position:absolute;z-index:1;
  width:260%;height:auto;
  pointer-events:none;
  will-change:transform;
  /* scaled well past the viewport so only the broad, open part of the curve
     is in frame — at smaller scales the lines read as scratches rather than
     as topography */
}
.bg-contours-a{
  top:26%;left:-78%;
  opacity:.13;
  transform:scale(3.4);
  animation:driftA 150s ease-in-out infinite alternate;
}
.bg-contours-b{
  bottom:4%;left:-60%;
  opacity:.08;
  transform:scaleY(-1) scale(2.8);
  animation:driftB 190s ease-in-out infinite alternate;
}
@keyframes driftA{
  from{transform:scale(3.4) translateX(0);}
  to{transform:scale(3.4) translateX(-1.6%);}
}
@keyframes driftB{
  from{transform:scaleY(-1) scale(2.8) translateX(0);}
  to{transform:scaleY(-1) scale(2.8) translateX(1.8%);}
}
/* the logo's tower mark, large and faint, sitting in the open centre column.
   With the clip gone this is what gives that space a subject — and it is the
   most direct answer to "match the logo" available to us. */
.bg-watermark{
  position:absolute;z-index:1;
  top:50%;left:50%;
  transform:translate(-50%,-54%);
  height:780px;width:auto;
  opacity:.13;
  pointer-events:none;
}
/* a plain vignette to seat the ticker and the header; no blackout needed now
   that nothing bright sits underneath */
.bg-scrim{
  position:absolute;inset:0;z-index:1;
  background:
    radial-gradient(130% 110% at 50% 44%, transparent 0%, transparent 46%, rgba(6,7,10,.46) 100%),
    linear-gradient(180deg,
      rgba(6,7,10,.34) 0%,
      rgba(6,7,10,.06) 12%,
      rgba(6,7,10,0) 30%,
      rgba(6,7,10,0) 70%,
      rgba(6,7,10,.26) 88%,
      rgba(6,7,10,.52) 100%);
}

@media (prefers-reduced-motion: reduce){
  .bg-contours{animation:none;}
}

/* ---------- frame / zones ---------- */
.frame{
  position:relative;z-index:2;
  height:1080px;
  display:flex;flex-direction:column;
}
/* RTL: the first child lands on the right, which is where the mockup puts the
   logo. No row-reverse — the DOM order is the visual order. */
/* ---------- the header rail (v8.1) ----------
   The header used to be two groups pinned to the two edges with ~990px of
   nothing between them. That hole was deliberate in v7.2 — a clip played behind
   the header and the gap was the window onto it — but v8 removed the clip, so
   the window became a hole. The header's atoms are now five cells spread across
   the full width, separated by one hairline component.

   The shared bands are the whole trick: five blocks sitting on one baseline
   read as a single rail, five blocks centred individually read as five
   floating islands. See --hdr-* above.

   CONTRAST, re-measured for v8.1: the old .zone-top::before is gone. It
   darkened the two edges of a bright daylight clip and kept the middle clear,
   and both halves of that are now wrong — the background is brand-built and
   dark, and text occupies the middle. Compositing the current background layers
   in maths and sampling 35 points across the band, with NO header scrim at all:
     --white      worst  9.72 : 1
     --dim        worst  6.65 : 1
     --gold-text  worst  6.48 : 1
     --dimmer     worst  4.66 : 1
   all above AA (4.5:1 small text, 3:1 large). The worst point is dead centre at
   the bottom of the band, where the brand watermark sits. Adding a scrim buys
   about +0.8 and costs the clean background, so there is none. Re-measure if the
   background layers change.

   v8.1.1 does not change a single colour, so those figures stand — but the size
   change moves the goalposts in our favour: at 28px the second band is now
   "large text" by WCAG (>=18.5pt), so its bar drops from 4.5:1 to 3:1 and the
   dimmest line on the rail clears it by more than double. */
.zone-top{
  position:relative;
  /* 196 rather than 176: the taller cells need it. The 20px comes out of the
     grid's 1fr row below (the ONE card), which had the slack — measured, not
     assumed; the Shabbat row underneath it is fixed at 300px and untouched. */
  flex:0 0 196px;
  display:flex;align-items:center;justify-content:space-between;
  gap:var(--s-8);           /* a floor, so nothing can collide on a long line */
  padding:0 54px;
  margin-top:24px;
}
/* one hairline closing the rail, inset to the same 54px as the cards below so
   the header reads as a band and not as content that trails off */
/* space-between hands every gap the same number of pixels. Measured, that is
   125px everywhere — arithmetically equal and optically wrong: 125px beside a
   107px logo reads as far more air than 125px beside a 440px block. The middle
   rule carries extra margin so the rail reads as two groups — identity on the
   reading side, live data on the far side — with the members of each group
   closer to one another than the groups are to each other. */
.zone-top > :nth-child(4){margin:0 var(--s-8);}
.zone-top::after{
  content:'';position:absolute;
  left:54px;right:54px;bottom:-11px;height:1px;
  background:linear-gradient(90deg,transparent,var(--gold-line) 10%,var(--gold-line) 90%,transparent);
  opacity:.62;   /* nudged with the type: a line that reads at a desk can
                    disappear at five metres, same as the 19px text did */
}
/* v7.2 — two columns with an open middle. RTL puts the first track on the
   right, so the lead column (resident notices) sits on the reading side, the
   open column is the wide 1fr in the centre, and the stack goes to the far
   side. The centre is not a gap between cards; it is the widest single element
   on the screen, and the clip plays in it. */
/* v8: the open centre was 762px because a video played in it. With the clip
   gone it only carries the brand mark, so the content panels took the width
   back.

   v8.2 takes the rest of it. Aviv's mockup makes the notices card the subject
   of the screen, and the tracks are measured off it rather than guessed: in the
   mockup the left column is 36% of the width, the notices card 49%, the gap
   between them 9%. At 1920 that is 690 / 946 with a 176px channel, which is
   what the 1fr plus two 30px gaps comes to. The centre is still open and the
   brand mark still shows through it — it is a channel now rather than a court. */
.zone-grid{
  flex:1 1 auto;min-height:0;
  display:grid;
  grid-template-columns:946px minmax(0,1fr) 690px;
  /* 30 and 54 are deliberately NOT on the spacing scale: the channel and the
     grid margin are what the card widths were measured against (906 + 30 +
     830 = 1766 inside 54px margins), so moving them to 32 and 48 would move
     every card edge that was signed off. A number that records a measurement
     is not design debt. */
  gap:30px;
  padding:0 54px;
  margin:var(--s-6) 0;
}
/* v8.2.3 — the single card sits on the RIGHT half (Aviv).
   906 = 1920/2 - 54: the canvas is half 1920 wide, less the grid's own 54px
   margin, so the card's inner edge lands on 960 — the exact centre line.

   In design-px, NOT calc(50vw - 54px), which is what this said for one
   version. Everything on this screen is a design-px on a fixed 1920 canvas
   that JS then scales to the monitor (see .fit); vw is the real browser
   window, so the two only agree when the window happens to be exactly 1920
   wide. Measured at a 1280 window the card came out 586px with its edge on
   640 — the "half the screen" rule quietly false on every screen but one.
   No viewport unit belongs inside this canvas.

   justify-content:start is the logical keyword, not `right`: in RTL the
   inline start IS the right, so this keeps meaning "the reading side" if the
   screen is ever composed for an LTR building. Which side it resolves to is
   measured below, not assumed. */
.zone-grid.is-single{
  grid-template-columns:906px;
  justify-content:start;
}
/* v8.3 — notices on the right, the stack on the left, and the notices card
   does NOT move: it keeps 960->1866 exactly as approved. 1812 of content less
   its 906 less the 30px channel leaves 876 for the left column, so the numbers
   are derived from the card that was already signed off rather than both
   columns being renegotiated. */
.zone-grid.is-pair{
  /* v8.7 — Aviv: the channel between the columns was too wide. 725 -> 830
     closes it from 181px to 76px and makes both left-hand cards 14.5% bigger.
     830 is the most the column can take: the Shabbat card's height is locked
     to its image's 2.151 aspect, so every pixel of width costs the ONE card
     above it half a pixel of height, and below about 290 the ONE card stops
     holding a hero and two headlines. space-between keeps the notices card on
     its approved 960->1866. */
  grid-template-columns:906px 830px;
  justify-content:space-between;
}
.zone-open{min-width:0;}
.zone-stack{
  display:grid;
  /* The Shabbat row is `auto` so it is exactly the card's own height, which
     comes from its width and its image's aspect; the ONE card takes whatever
     is left. That way --shab-w stays the single control: widen it and the row
     below grows, the row above shrinks, and nothing has to be kept in step by
     hand. */
  grid-template-rows:1fr auto;
  gap:var(--s-6);
  min-height:0;
}
/* A lone panel takes the LOWER slot and leaves the upper one visibly open,
   which is what "room for another frame above it" means. With a second panel
   configured both rows fill in order and this stops applying on its own. */
.zone-stack > :only-child{grid-row:2;}
.zone-ticker{flex:0 0 88px;}

/* A live card that has nothing in it yet takes itself out of the layout, the
   same way an empty header cell does (brief §18): on a lobby wall an empty
   frame is worse than a gap. The shell is still in the DOM, which is the point
   — it is where the browser puts the feed when it gets it, and the card
   appears at that moment rather than waiting for the next rebuild. */
.card.is-empty{display:none;}

/* ---------- shared card ---------- */
.card{
  position:relative;
  min-height:0;                 /* lets inner lists clip instead of blowing the row */
  display:flex;flex-direction:column;
  overflow:hidden;
  background:linear-gradient(180deg, var(--card-top) 0%, var(--card-bot) 100%);
  border:1px solid var(--frame);
  border-radius:var(--radius);
  padding:var(--pad);
  box-shadow:0 26px 60px -30px rgba(0,0,0,.9);
}
/* label row: mark on the right, title beside it, a hairline across the gap and
   the mockup's small chevron closing the line on the left */
.card-label{
  flex:0 0 auto;
  display:flex;align-items:center;gap:var(--s-4);
  height:40px;
  margin-bottom:var(--s-4);
  font-size:var(--t-meta);font-weight:600;
  color:var(--gold-text);
  letter-spacing:.02em;
}
.card-label-mark{flex:0 0 auto;display:flex;align-items:center;}
.card-label-mark img{height:26px;width:auto;}
.card-label-mark svg{width:24px;height:24px;}
.card-label-rule{
  flex:0 0 auto;
  width:1px;height:24px;
  background:var(--gold-line);
}
.card-label-text{flex:0 0 auto;white-space:nowrap;}
.card-label-fill{
  flex:1 1 auto;height:1px;
  background:linear-gradient(90deg, rgba(157,147,121,.38), transparent);
}
.card-label-chev{flex:0 0 auto;width:15px;height:15px;color:var(--gold);}

/* ---------- the ONE card's frame (v8.6) ----------
   The other two cards are Aviv's own images. There is no reference for a
   sports card, so this frame is BUILT to match them rather than cut out of
   one: the same conic metal, the same radius, the same bloom. It is the only
   card on screen whose chrome is CSS, and if a reference turns up it swaps to
   the image treatment the same way the other two did. */
.w-feed{
  /* v8.7 — the card is 830 wide now but 290 tall rather than 337, because the
     Shabbat card below it took the height when it took the width. Aviv also
     asked for the text to be bigger. Both at once only works by spending the
     saved lines on size: the headline goes 27->30 and the list titles 18->21,
     the standfirst goes entirely, and the budget comes to label 32+8, hero
     81+8, two rows at 61, padding 14+12, border 4 = 281 of 290. Measured on
     the page, not estimated on paper. */
  padding:var(--s-4) var(--s-6) var(--s-3);
  border:2px solid transparent;
  border-radius:var(--r-card);
  background:
    linear-gradient(180deg, var(--card-top) 0%, var(--card-bot) 100%) padding-box,
    conic-gradient(from 212deg at 50% 50%,
      #6F5F39 0%,   #D8C08C 7%,   #FAF2D8 13%,  #A89059 21%,
      #7C6B3E 29%,  #D4AC2E 38%,  #F8EECE 45%,  #9E8752 53%,
      #6F5F39 60%,  #C9A227 69%,  #FAF2D8 77%,  #A89059 85%,
      #8A7646 93%,  #6F5F39 100%) border-box;
  box-shadow:
    0 0 70px -10px rgba(201,162,39,.42),
    0 0 22px -6px rgba(201,162,39,.24),
    0 26px 60px -30px rgba(0,0,0,.9);
}

/* ---------- FeedPanel — shared by the Ynet and ONE cards ----------
   Both are the same component: a rotating hero (image, category tag, headline,
   standfirst) over a static list of secondary updates. Only the partner logo,
   the label and the data source differ, so this lives once. */
.w-feed-hero{
  position:relative;
  flex:0 0 336px;
  margin-bottom:var(--s-4);
}
.w-feed-hero-item{
  position:absolute;inset:0;
  display:flex;flex-direction:column;
  opacity:0;
  transition:opacity 1200ms ease;
}
.w-feed-hero-item.is-active{opacity:1;}
.w-feed-media{
  position:relative;
  flex:0 0 198px;
  border-radius:var(--r-media);
  overflow:hidden;
  background:rgba(255,255,255,.04);
}
.w-feed-media img{width:100%;height:100%;object-fit:cover;transform-origin:center;}
.w-feed-media::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg, rgba(8,9,12,.42) 0%, rgba(8,9,12,0) 46%, rgba(8,9,12,.34) 100%);
}
.w-feed-tag{
  position:absolute;top:var(--s-3);left:var(--s-3);z-index:2;
  font-size:var(--t-meta);font-weight:600;letter-spacing:.02em;
  color:#fff;
  background:#D8232A;                 /* Ynet red, from their own roundel */
  border-radius:var(--r-item);
  padding:var(--s-1) var(--s-3);
}
.is-sport .w-feed-tag{background:#2E4E9E;}   /* ONE blue */
.w-feed-copy{display:flex;flex-direction:column;min-width:0;}
.w-feed-title{
  margin-top:var(--s-4);
  font-size:var(--t-head);font-weight:800;line-height:var(--lh-head);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
}
.w-feed-standfirst{
  margin-top:var(--s-2);
  font-size:var(--t-body);font-weight:300;line-height:var(--lh-body);
  color:var(--dim);
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;
  overflow:hidden;
}

/* wide variant: short card, so the hero image sits BESIDE its headline
   instead of above it. Same component, same data — only the axis changes.

   v9.1 rebalanced it against the type scale. The timestamp used to sit on its
   own line above each headline; at the scale's 22px floor two such rows need
   158px and the card has 118. It is now INLINE with the headline instead of
   being dropped — the row loses a line, not a fact.

   Budget, measured: padding 16+12, border 4, label 32+8, hero 92+8, two rows
   at 50 = 272 of 290. */
.is-wide .card-label{height:32px;margin-bottom:var(--s-2);}
.is-wide .card-label-mark img{height:25px;}
.is-wide .w-feed-hero{flex:0 0 92px;margin-bottom:var(--s-2);}
.is-wide .w-feed-hero-item{flex-direction:row;gap:var(--s-4);align-items:stretch;}
/* the box takes its SHAPE from the source: ONE serves every image at 636x326,
   so deriving the width from the height at that ratio crops nothing */
.is-wide .w-feed-media{flex:0 0 auto;height:100%;aspect-ratio:636 / 326;}
.is-wide .w-feed-copy{flex:1 1 auto;justify-content:center;}
.is-wide .w-feed-title{margin-top:0;-webkit-line-clamp:2;}
/* the standfirst stays out of this variant: it was the least readable line on
   the card and the room it took is what the second headline needed */
.is-wide .w-feed-standfirst{display:none;}
/* 8, not 12: with the timestamp inline the row's height is set by the
   THUMBNAIL, not by the text — budgeting on the text put the list 17px over
   and it clipped silently behind overflow:hidden. */
.is-wide .w-feed-item{padding:var(--s-2) 0;}
.is-wide .w-feed-item:first-child{padding-top:0;}
.is-wide .w-feed-thumb{flex:0 0 auto;height:38px;aspect-ratio:636 / 326;}
/* time and headline on one line. One clamped line, not two: at two lines a
   single long headline grows its row and pushes the next one out of the card,
   and a deterministic row beats a headline that clips on a slow news day. */
.is-wide .w-feed-item-body{display:flex;align-items:baseline;gap:var(--s-3);min-width:0;}
.is-wide .w-feed-time{flex:0 0 auto;}
.is-wide .w-feed-item-title{
  flex:1 1 auto;min-width:0;margin-top:0;
  font-size:var(--t-body);-webkit-line-clamp:1;
}

.w-feed-list{
  flex:1 1 auto;min-height:0;
  display:flex;flex-direction:column;
  overflow:hidden;
}
/* mockup puts the thumbnail on the LEFT of each row; the DOM keeps the
   text-first order and the row is reversed visually */
.w-feed-item{
  display:flex;flex-direction:row-reverse;
  gap:var(--s-3);align-items:center;
  padding:var(--s-3) 0;
  border-top:1px solid var(--line);
}
.w-feed-item:first-child{border-top:none;padding-top:0;}
.w-feed-thumb{
  flex:0 0 78px;height:54px;
  border-radius:var(--r-item);overflow:hidden;
  background:rgba(255,255,255,.04);
}
.w-feed-thumb img{width:100%;height:100%;object-fit:cover;}
.w-feed-item-body{flex:1 1 auto;min-width:0;}
.w-feed-time{
  font-size:var(--t-meta);font-weight:600;
  color:var(--gold-text);
  letter-spacing:.02em;
  font-variant-numeric:tabular-nums;
}
.w-feed-item-title{
  margin-top:var(--s-1);
  font-size:var(--t-head);font-weight:400;line-height:var(--lh-head);
  color:var(--white);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
}

/* ---------- header rail: the cells ----------
   One system, five cells. A cell is a fixed height and its widget is a column
   of two bands; because every cell declares the same heights from the same
   tokens, the big lines all land on one baseline and the small lines on a
   second. That is what the rail is. */
.hdr-cell{height:var(--hdr-cell-h);display:flex;align-items:center;}
.hdr-cell > *{display:flex;flex-direction:column;}
/* A cell whose data never arrived takes itself AND its separator out of the
   rail, rather than leaving a hairline floating next to a gap (brief §18).
   This is why each hairline follows its cell instead of preceding it, and why
   the cell divs carry no whitespace inside them. */
.hdr-cell:empty,
.hdr-cell:empty + .hdr-rule{display:none;}

/* band 1 — the one big line of each cell, sitting on the shared baseline */
.w-welcome-title,
.w-wx-now-main,
.w-clock-time{
  height:var(--hdr-lede-h);
  display:flex;align-items:flex-end;
}

/* band 2 — the quiet line under it */
.w-welcome-sub,
.w-wx-now-cond,
.w-clock-date{
  height:var(--hdr-sub-h);
  margin-top:var(--hdr-band-gap);
  display:flex;align-items:center;
  font-size:var(--t-body);font-weight:300;
}
/* the separator — defined once, used four times */
.hdr-rule{
  flex:0 0 1px;
  width:1px;height:138px;
  background:linear-gradient(180deg,transparent,var(--gold-line) 20%,var(--gold-line) 80%,transparent);
}

/* ---------- BrandWidget (rail cell 1, far right) ----------
   The client's logo exists only as a vertical lockup — mark over wordmark over
   tagline, no horizontal variant in their source files. The rail is built tall
   enough to carry it whole rather than cutting it down to fit, so this is the
   one cell that opts out of the two-band grid. */
.hdr-brand{height:auto;}
.w-brand img{height:172px;width:auto;}

/* ---------- WelcomeWidget (rail cell 2) ----------
   v9.4 — Aviv asked for the greeting at the size of the clock and the
   temperature, on one line with them. So it takes the numeral size itself
   rather than a number that happens to match: --t-numeral, not 104px.

   LINE-HEIGHT IS THE PART THAT MAKES THEM SHARE A BASELINE. The band is 96px
   with the three cells bottom-aligned, so equal font sizes are not enough —
   what lands on the shared baseline is the bottom of the LINE BOX, and the
   half-leading under it has to match too. At 1.05 this line box was 109.2px
   against the numerals' 95.7, which floated the greeting 6.8px above the
   digits: close enough to look like a mistake rather than a decision. .92 is
   the numeral component's own value, copied here deliberately.

   TRACKING COMES DOWN WITH THE SIZE GOING UP, .14em -> .04em. The old .14 was
   there to open up a 58px line; at 104px it only added 189px of air to a
   phrase that now has to share the rail with three other cells, and large
   display type wants less tracking, not the same amount. */
.w-welcome-title{
  font-size:var(--t-numeral);font-weight:600;
  letter-spacing:.04em;
  color:var(--gold-text);
  line-height:.92;
}
/* centred under the greeting (Aviv), which is also why the hairlines read as a
   pair now rather than as a line that starts at the right edge */
.w-welcome-sub{
  justify-content:center;
  gap:var(--s-4);
  letter-spacing:0;          /* reading size: tracking breaks Hebrew word-shape */
  color:var(--white);
}
.w-welcome-sub::before,
.w-welcome-sub::after{
  content:'';height:1px;width:58px;flex:0 0 58px;
  background:linear-gradient(90deg,transparent,var(--gold-line),transparent);
}

/* ---------- WeatherNowWidget ---------- */
.w-wx-now-main{gap:var(--s-6);}
/* the icon is optically heavier than the numeral, so it sits a touch above the
   baseline rather than on it. Scaled with the reading: 84 against the clock's
   104 is the same ratio the 62 held against the old 78. */
.w-wx-now-icon{flex:0 0 auto;width:84px;height:84px;margin-bottom:var(--s-3);}
.w-wx-now-temp{display:flex;align-items:flex-end;}
/* city · condition. The middot is drawn rather than typed so the two halves
   stay separate elements — the city is server data, the condition is ours. */
.w-wx-now-cond{gap:var(--s-3);letter-spacing:.02em;color:var(--dim);}
.w-wx-now-city + span::before{
  content:'';display:inline-block;vertical-align:.28em;
  width:4px;height:4px;margin-inline-end:12px;border-radius:50%;
  background:var(--gold);
}

/* ---------- ClockWidget (rail cell 5, far left) ---------- */
/* The last cell aligns to the screen's outer edge, mirroring the logo on the
   right, so the rail has a clean margin at both ends. In RTL that edge is
   flex-end — except on the time itself, which is direction:ltr so the digits
   render as one LTR run, which flips the meaning of the keyword. */
.w-clock-date{justify-content:flex-end;color:var(--dim);letter-spacing:.02em;}
/* The clock carries .num itself — it does NOT restate the face and the size.
   It did, for one version, and the copy was missing line-height: .num sets .92
   and the copy inherited `normal`, so the two boxes were different heights and
   bottom-aligning them put the clock's glyphs 28px above the temperature's.
   Aviv spotted it on screen. Anything that restates a component's type is a
   copy waiting to drift; this only adds what is genuinely clock-specific. */
/* The clock carries .num itself — it does NOT restate the face and the size.
   It did, for one version, and the copy was missing line-height, which put
   its glyphs 28px above the temperature's. Anything that restates a
   component's type is a copy waiting to drift. */
.w-clock-time{justify-content:flex-start;}

/* ---------- display numerals ----------
   v9.0 — Aviv asked for the clock and the temperature to carry exactly the
   font the Shabbat times do, for uniformity. Measured rather than assumed:
   those times resolve to Heebo 700, tabular-nums, #FBFAF7. So that is what
   this is now, and Fraunces is out of the header.

   THE FIXED CELLS WENT WITH IT. They existed only because Fraunces has no
   tabular figures and an always-on clock shuffled sideways every minute.
   Heebo's digits are naturally monospaced — measured, '1111' and '0000' are
   both 231px at 100px even with font-variant-numeric off — so the cells had
   nothing left to do, and machinery kept past its reason is the thing that
   drifts later. tabular-nums stays as the explicit statement of intent.

   What survives is the gold colon: that is a colour, not a font, and it is
   what makes the figure read as a clock without anything having to move.

   WEIGHT 600, NOT 700. Checking what the browser had actually loaded turned
   up every 700 on the screen being SYNTHESISED — Heebo ships 100/300/400/600
   /800 here and nothing else, so 700 was faux bold drawn from 600, which is
   the exact trap recorded at the top of this file and which already shipped
   once in v7.2. It was not only the clock: the Shabbat times, the parasha and
   the notices card's title had all been sitting on a fake weight since they
   were written. All four are real 600 now, so they render the same way in
   every browser instead of each one inventing its own bold. */
.num{
  /* a reading is an LTR run: without this the RTL context lays "30°" out as
     "°03", which is exactly what it did the first time */
  direction:ltr;
  font-family:var(--sans);
  font-size:var(--t-numeral);font-weight:600;line-height:.92;
  letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;
}
.num-sep{color:var(--gold-text);padding:0 .02em;}
/* the degree sign is a mark, not a glyph at reading size */
.num-x{font-size:.46em;align-self:flex-start;margin-top:.14em;}

/* ---------- ShabbatWidget (v8.4) ----------
   The card IS Aviv's image. assets/images/shabbat-card.jpg is his reference
   with the nine variable strings erased out of it; everything visible — frame,
   gold sweep, photo, panel, all five icons, both hairlines, the title — is his
   pixels, and this block only positions live text back into the holes.

   That is the whole point: there is no second implementation of the design to
   drift away from the first one.

   ONE NUMBER CONTROLS THE SIZE. --shab-w is the card's width and the height
   follows from the image's own 1535:714. At 725 the card fits beside a second
   frame of equal height in this column (337 each), which is the layout Aviv
   asked for, and the type lands at the reference's own proportions: see the
   cap-height table below. Widening it to 876 would make the card 407 tall and
   leave 267 for the frame above instead of an equal 337.

   TYPE. Cap heights were measured in the source and divided by its card height
   (714), then multiplied by ours. The px values below are what that came to at
   a 725px card; they are written as cqw — a percentage of the card's own width
   — so widening --shab-w carries the type with it instead of leaving a second
   set of numbers to update by hand. Heebo's cap is about .72 of its em:
     time    77px cap -> 10.78% -> 50px font
     label   21px cap ->  2.94% -> 14px
     city    20px cap ->  2.80% -> 13px
     bottom label 18-19px      -> 12-13px
     bottom value 28-30px      -> 19-20px
   Colours are sampled from the source too, which is how the bottom row turned
   out to be gold on the right and white on the left rather than one or the
   other. */
.w-shabbat{
  --shab-w:830px;
  position:relative;
  /* container units below are a percentage of THIS, so the type scales with
     the card instead of being a second set of numbers to remember */
  container-type:inline-size;
  /* Every size on this card is written as a multiple of --shab-u, so each one
     is ONE number and the unit is decided in a single place. See the @supports
     block below for why that matters. */
  --shab-u:1cqw;
  width:var(--shab-w);
  aspect-ratio:1535 / 714;
  align-self:end;                 /* sits on the floor of its grid row */
  padding:0;border:0;border-radius:0;background:none;box-shadow:none;
  overflow:visible;
}
.w-shabbat-art{width:100%;height:100%;display:block;}

/* every live string: positioned by its measured centre, so a longer or shorter
   value grows about that point exactly as the source's did */
.w-shabbat-f{
  position:absolute;
  transform:translate(-50%,-50%);
  white-space:nowrap;
  line-height:1;
  pointer-events:none;
}
.f-city       {left:14.07%;top:13.17%;font-size:calc(1.793 * var(--shab-u));font-weight:300;letter-spacing:.04em;color:#EFEEEE;}
.f-lbl-candle {left:48.11%;top:34.45%;font-size:calc(1.931 * var(--shab-u));font-weight:400;letter-spacing:.03em;color:#E7C99C;}
.f-lbl-hav    {left:20.13%;top:34.36%;font-size:calc(1.931 * var(--shab-u));font-weight:400;letter-spacing:.03em;color:#E7C99C;}
.f-time-candle,
.f-time-hav   {font-size:calc(6.897 * var(--shab-u));font-weight:600;letter-spacing:.005em;color:#FBFAF7;
               direction:ltr;font-variant-numeric:tabular-nums;}
.f-time-candle{left:49.51%;top:47.13%;}
.f-time-hav   {left:19.61%;top:47.13%;}
/* v8.5 — the haftarah came out (Aviv), and the book icon and the divider that
   split this row came out of the chrome with it. The parasha therefore owns
   the whole row: centred on the panel rather than on the old right-hand cell,
   and at roughly double the size it had when it was sharing. */

.f-lbl-parasha{left:34.5%;top:71.2%;font-size:calc(2.345 * var(--shab-u));font-weight:300;letter-spacing:.08em;color:#ECCF96;}
.f-val-parasha{left:34.5%;top:78.4%;font-size:calc(5.241 * var(--shab-u));font-weight:600;color:#F9DFAE;}

/* TV INSURANCE (v9.6). Container query units are Chrome 105, August 2022. The
   screen runs in an Android TV's WebView, which updates through the Play Store
   and on a new set will be far past that — but the WebView version is the one
   thing about the target we do not control, and on an old one every size above
   would be an invalid value and the whole card would fall back to the body's
   font. Not a degraded card: a broken one, on the wall, with no way to know.

   One line fixes it, because the unit is named once. 1cqw is one percent of
   --shab-w, so at 830px it is 8.3px — any browser that cannot resolve cqw gets
   exactly the sizes a modern one computes, and a browser that can never sees
   this rule. Keep the two in step if --shab-w ever moves: 8.3 = 830 / 100. */
@supports not (font-size: 1cqw){
  .w-shabbat{ --shab-u:8.3px; }
}

/* ---------- NoticesWidget (v8.5) ----------
   Same concept as the Shabbat card, which is what Aviv asked for: the card IS
   his image (mockups/v8-2-notices.png) with the variable strings erased out of
   it, and live text positioned back into the holes. Nothing about the look is
   implemented here any more — frame, panel, highlight, sweep, bell, title,
   hairline, pip, chevrons and the rule under the title are all his pixels.

   Geometry below is measured from that file: its card is 782x587, and every
   position is the centre of the string it replaces, as a percentage, so the
   values grow about the same points the originals did.

   ERASING THE GLYPH NEEDED A DIFFERENT FILL from the rest. It sits directly
   under the panel's top highlight, and a vertical interpolation dragged that
   bright line all the way down the patch — a gold smear through the middle of
   the card. It is filled horizontally instead, with no padding on its top
   edge, so the highlight is never sampled.

   TYPE from the same measurements, scaled by card height (587 -> 680):
     title 75px box -> 12.78% -> 62px      body 26-31px -> ~30px
     date  27px     ->  4.60% -> 30px      glyph 64px   -> 74px */
.w-notices{
  position:relative;
  width:906px;
  aspect-ratio:782 / 587;
  align-self:center;
  padding:0;border:0;border-radius:0;background:none;box-shadow:none;
  overflow:visible;
}
.w-notices-art{width:100%;height:100%;display:block;}

/* v8.6 — drawn rather than baked, because Aviv wanted it centred and the
   image has it on the right. Size and colour are measured off the source:
   30px cap on its 587px card, which is 48px here, at #EFD09D. */
.w-notices-head{
  position:absolute;left:50%;top:10.6%;
  transform:translate(-50%,-50%);
  display:flex;align-items:center;gap:var(--s-4);
  margin:0;
  font-size:var(--t-card);font-weight:600;line-height:var(--lh-display);
  color:#EFD09D;
  white-space:nowrap;
}
.w-notices-head svg{width:44px;height:44px;flex:0 0 auto;}

.w-notices-deck{position:absolute;inset:0;}
.w-notice{
  position:absolute;inset:0;
  opacity:0;
  transition:opacity 1000ms ease;
}
.w-notice.is-active{opacity:1;}

.w-notice-f{
  position:absolute;
  transform:translate(-50%,-50%);
  line-height:1.25;
  margin:0;
  pointer-events:none;
}
.f-icon{
  left:48.08%;top:27.60%;
  width:74px;height:74px;
  color:#C8A771;
}
.f-title{
  left:50.06%;top:41.31%;
  font-size:var(--t-display);font-weight:800;letter-spacing:-.005em;
  color:#F5F3EC;white-space:nowrap;
}
.f-body{
  left:50.45%;top:63.9%;
  width:560px;                   /* the reference's own measure, scaled */
  font-size:var(--t-body);font-weight:300;line-height:var(--lh-body);
  text-align:center;color:#E4E3E2;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
}
.f-date{
  left:49.04%;top:78.45%;
  direction:ltr;                 /* icon left of the numerals, as in the image */
  display:flex;align-items:center;gap:var(--s-3);
  font-size:var(--t-body);font-weight:400;letter-spacing:.01em;
  color:#D3C8B4;white-space:nowrap;
}
.f-date svg{width:28px;height:28px;flex:0 0 auto;}

/* redrawn rather than baked: the image has five and a building has as many as
   it has. Sizes and colours are the image's own. */
.w-notices-dots{
  position:absolute;left:48.21%;top:93.78%;
  transform:translate(-50%,-50%);
  direction:ltr;
  display:flex;align-items:center;gap:var(--s-4);
}
.w-notices-dots span{
  width:9px;height:9px;border-radius:50%;
  background:#555554;
  transition:background 600ms ease;
}
.w-notices-dots span.is-active{background:#FFD064;}

/* ---------- TickerWidget (brief §9) ----------
   Full-bleed bar across the very bottom edge — not a floating card.

   v8.2.4 — scaled up and the band raised a little (Aviv: more prominent).
   Everything in it moves together, 88->118 tall with the type and the lockup
   at the same ratio, so it reads as one band that grew rather than a bar with
   bigger text crammed into it. The 30px comes out of the grid row above, which
   is measured below rather than assumed to have the slack. */
.w-ticker{
  position:relative;
  height:118px;
  display:flex;align-items:center;gap:var(--s-8);
  padding:0 var(--s-12);
  background:linear-gradient(180deg, rgba(10,11,14,.94) 0%, rgba(6,7,9,.98) 100%);
  /* the rule is the band's top edge against a dark screen — it carries more of
     the "this is a thing" than the height does, so it gains with it */
  border-top:1px solid rgba(201,162,39,.72);
  box-shadow:0 -14px 34px -22px rgba(201,162,39,.40);
  overflow:hidden;
}
.w-ticker-logo{flex:0 0 auto;height:46px;width:auto;}
.w-ticker-kicker{
  flex:0 0 auto;
  font-size:var(--t-body);font-weight:400;letter-spacing:.02em;
  color:var(--gold-text);
  white-space:nowrap;
}
.w-ticker-sep{flex:0 0 auto;width:1px;height:56px;background:var(--gold-line);}
.w-ticker-flow{
  flex:1 1 auto;min-width:0;
  overflow:hidden;white-space:nowrap;
  /* RTL marquee gap fix: the two-copies + translateX(-50%) technique grows a
     gap in RTL. Forcing LTR on the flow element only (the Hebrew runs inside
     still shape RTL via bidi) keeps it seamless. See README. */
  direction:ltr;
  /* The fade is ASYMMETRIC, and it was asymmetric the wrong way round.
     Measured: a Hebrew headline's sentence starts at the RIGHT edge of its
     span, and the track travels leftward, so spans enter from the right —
     which means every sentence's opening words arrived through an 85px fade
     while its already-read tail left through a 42px one. The first thing a
     reader needs was the washed-out thing. Narrow on entry, wide on exit. */
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 98%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 98%,transparent);
}
/* headlines and separators are siblings of the inline-flex track, not nested
   spans: a separator sitting after a Hebrew run inside a direction:ltr line
   gets reordered by bidi and lands on the wrong side. As flex items they are
   placed by the flex algorithm, which ignores bidi. */
.w-ticker-track{
  display:inline-flex;align-items:center;gap:var(--s-12);
  /* Speed, not duration, is the thing that has to stay constant: 90.5 px/sec
     is what makes a headline readable as it crosses. The duration that
     produces it is set from the measured track width in front-page.php,
     because the track's LENGTH depends on the headlines currently in it — it
     measured 16837 design-px one afternoon and 15567 the next, same type. A
     fixed duration therefore drifts with the news, which the earlier
     calibration here did not account for.
     The value below is only the fallback for a page where JS never runs. */
  animation:ticker 93s linear infinite;
  font-size:var(--t-head);font-weight:400;
  color:var(--white);
}
.w-ticker-track span{flex:0 0 auto;}
.w-ticker-track i{flex:0 0 auto;width:1px;height:34px;background:var(--gold-line);}
@keyframes ticker{from{transform:translateX(0);}to{transform:translateX(-50%);}}
/* the client's own topographic strip closes the bar, standing in for the gold
   skyline in the mockup — their asset rather than an invented one */
.w-ticker-orn{
  position:absolute;left:0;bottom:0;z-index:0;
  height:118px;width:520px;
  object-fit:cover;object-position:left bottom;
  opacity:.20;
  pointer-events:none;
  mask-image:linear-gradient(90deg,#000,transparent);
  -webkit-mask-image:linear-gradient(90deg,#000,transparent);
}

/* ---------- animation system ----------
   Brief §10: slow and elegant. Two mechanisms only — a staggered reveal on
   load and a very slow Ken Burns on every hero image. Everything that changes
   afterwards is a crossfade driven from front-page.php. */
@keyframes reveal{
  from{opacity:0;transform:translateY(26px);}
  to  {opacity:1;transform:translateY(0);}
}
.reveal{opacity:0;animation:reveal 900ms cubic-bezier(.22,.68,.28,1) forwards;}
/* the rail assembles right to left, cell by cell, the order a reader's eye
   takes them — not all five at once, which looked like a page load */
.hdr-brand{animation-delay:120ms;}
.hdr-greet{animation-delay:220ms;}
.hdr-wx{animation-delay:320ms;}
.hdr-time{animation-delay:420ms;}
/* right to left, the order a reader's eye takes them */
.zone-grid > .card{animation-delay:560ms;}
.zone-stack > .card:nth-child(1){animation-delay:740ms;}
.zone-stack > .card:nth-child(2){animation-delay:900ms;}
.zone-ticker .reveal{animation-delay:1280ms;}

@keyframes kenburns{from{transform:scale(1);}to{transform:scale(1.07);}}
.kb{animation:kenburns 26s ease-in-out infinite alternate;}

/* ---------- stale-data badge + refresh fade (brief §17-18) ----------
   Deliberately small and quiet: it exists so a resident glancing at a frozen
   screen has one honest hint, not so it competes with the content. */
.stale-badge{
  position:absolute;left:22px;bottom:102px;z-index:5;
  font-size:var(--t-meta);font-weight:300;letter-spacing:.02em;
  color:var(--dimmer);
  background:rgba(10,11,14,.72);
  border:1px solid var(--frame-soft);
  border-radius:999px;
  padding:var(--s-2) var(--s-4);
  opacity:0;
  transition:opacity 600ms ease;
  pointer-events:none;
}
.stale-badge.is-on{opacity:1;}
body.is-refreshing .fit{opacity:0;transition:opacity 700ms ease;}

/* A lobby screen has no reader who can opt out, but honour the OS flag anyway —
   it also makes the design reviewable in a still screenshot. */
@media (prefers-reduced-motion: reduce){
  .kb,.w-ticker-track{animation:none;}
  .reveal{animation-duration:1ms;}
  .w-feed-hero-item,.w-notice,.w-notices-dots span{transition:none;}
}

/* ============================================================================
   DESIGN VARIANT — "contrast"
   ============================================================================
   A second design, switched on by data-theme="contrast" on <html>. Nothing
   above this line changes, so the original is always one attribute away; the
   default build still renders it.

   WHAT THIS IS NOT. It is not a contrast fix for the text. The whole screen
   was audited first and every text element measures between 7.78:1 and
   18.85:1 against AA bars of 4.5 and 3 — the worst thing on it is ONE's blue
   category badge at 7.78. There was nothing to rescue, and claiming otherwise
   would have been easy and false.

   WHAT IT IS. The real weakness the audit exposed is SEPARATION, which is a
   different problem from contrast: the cards are translucent panels over a
   textured ground, so a card's edge is soft and a card reads as a tint rather
   than a surface. This variant makes each card an opaque surface on a quieter
   ground, lifts the small type, and opens the body line-height — the things a
   resident at five metres, or one who is eighty, actually feels.

   WHAT IT CANNOT DO. Two of the three cards are Aviv's own artwork: their
   frame, panel and gold are pixels in a jpg. The variant can change the live
   text drawn over them and the ground behind them. It cannot repaint them,
   and nothing here pretends to. */

/* --- the ground steps back so the cards are the lit things on it --- */
[data-theme="contrast"]{
  /* Pushed harder than the first attempt. Side by side the two designs were
     almost indistinguishable, which makes a comparison worthless — if a
     variant is not obviously a choice it is not a variant. The panels are now
     a clear step LIGHTER than the ground rather than a tint over it. */
  --card-top:rgba(40,42,49,0.985);
  --card-bot:rgba(28,30,36,0.99);
  --frame:rgba(201,188,156,0.62);
  --line:rgba(255,255,255,0.15);
  --dim:rgba(244,243,239,0.90);
  --dimmer:rgba(244,243,239,0.74);
}
[data-theme="contrast"] .bg-base{
  background:
    radial-gradient(1000px 700px at 50% 42%, rgba(157,147,121,.07), transparent 70%),
    linear-gradient(168deg, #101114 0%, #0B0C0F 46%, #050607 100%);
}
[data-theme="contrast"] .bg-contours-a{opacity:.035;}
[data-theme="contrast"] .bg-contours-b{opacity:.02;}
[data-theme="contrast"] .bg-watermark{opacity:.045;}

/* --- the ONE card becomes a surface with an edge, not a tint --- */
[data-theme="contrast"] .w-feed{
  background:
    linear-gradient(180deg, rgba(42,44,51,.985) 0%, rgba(28,30,36,.99) 100%) padding-box,
    conic-gradient(from 212deg at 50% 50%,
      #6F5F39 0%,   #D8C08C 7%,   #FAF2D8 13%,  #A89059 21%,
      #7C6B3E 29%,  #D4AC2E 38%,  #F8EECE 45%,  #9E8752 53%,
      #6F5F39 60%,  #C9A227 69%,  #FAF2D8 77%,  #A89059 85%,
      #8A7646 93%,  #6F5F39 100%) border-box;
  box-shadow:
    0 0 80px -8px rgba(201,162,39,.38),
    0 0 24px -4px rgba(201,162,39,.22),
    0 30px 70px -32px rgba(0,0,0,.95);
}
/* a rule under the label and heavier rules between rows: the card stops being
   one undivided block of text */
[data-theme="contrast"] .w-feed .card-label{
  border-bottom:1px solid rgba(201,188,156,.34);
}
[data-theme="contrast"] .w-feed-item{border-top-color:rgba(255,255,255,.20);}
[data-theme="contrast"] .w-feed-media{border:1px solid rgba(201,188,156,.26);}
/* the thumbnails get an edge too, so a row is three distinct things rather
   than a photo bleeding into the panel */
[data-theme="contrast"] .w-feed-thumb{border:1px solid rgba(255,255,255,.14);}

/* --- small type up, line-height open --- */
/* the Shabbat card's live text is absolutely positioned, so size here costs
   no layout at all — this is the card the audit found the smallest text in
   (15px city, 16px labels) and the one the base cannot touch without breaking
   the proportions measured off Aviv's mockup */
/* through --shab-u like the base, so the variant inherits the TV fallback
   instead of being the one place that still breaks on an old WebView */
[data-theme="contrast"] .f-city       {font-size:calc(2.40 * var(--shab-u));}
[data-theme="contrast"] .f-lbl-candle,
[data-theme="contrast"] .f-lbl-hav    {font-size:calc(2.55 * var(--shab-u));font-weight:400;}
[data-theme="contrast"] .f-lbl-parasha{font-size:calc(2.85 * var(--shab-u));}
[data-theme="contrast"] .f-time-candle,
[data-theme="contrast"] .f-time-hav   {font-size:calc(7.20 * var(--shab-u));}
[data-theme="contrast"] .f-val-parasha{font-size:calc(5.60 * var(--shab-u));}

/* ONE's sizes are NOT overridden here any more. These four rules used to lift
   them, and once the base moved onto the type scale they were lifting them
   DOWNWARDS — 18px over a 22px base, 22 over 30. A variant that quietly
   shrinks the thing it exists to enlarge is worse than no variant, and this is
   what happens when one design hardcodes numbers the other one owns. The base
   scale carries both designs; this variant only changes what is genuinely its
   own. */

/* the notices card's body opens up — the one long paragraph on the screen */
[data-theme="contrast"] .f-body{line-height:1.58;color:#EFEEED;}
[data-theme="contrast"] .f-date{color:#E3D8C2;}

/* --- the header rail and the ticker get their edges back --- */
[data-theme="contrast"] .hdr-rule{
  background:linear-gradient(180deg,transparent,rgba(201,188,156,.75) 20%,rgba(201,188,156,.75) 80%,transparent);
}
[data-theme="contrast"] .zone-top::after{opacity:.85;}
[data-theme="contrast"] .w-ticker{
  background:linear-gradient(180deg, rgba(16,17,21,.97) 0%, rgba(9,10,13,.99) 100%);
  border-top-color:rgba(201,162,39,.88);
}
[data-theme="contrast"] .w-ticker-sep{background:rgba(201,188,156,.60);}
[data-theme="contrast"] .w-ticker-track i{background:rgba(201,188,156,.50);}
