/* Crochet BI — HTML port of the Power BI reports.

   The Power BI pages are fixed canvases (1700x750, 2100x1320, …) with every
   visual pinned to a pixel. Reproducing that literally would give a page that
   only looks right on one screen, so the *content* is reproduced exactly and
   the *layout* is reflowed. Reading order, grouping and colour coding follow
   the originals card for card.

   ── TYPE ────────────────────────────────────────────────────────────────
   Everything sizes off ONE scale, declared once below. The previous revision
   grew thirteen ad-hoc sizes (.64 / .66 / .68 / .70 / .72 / .75 / .85 / 1.2 /
   1.25 / 2 / 2.1 / 2.2 / 2.4rem), most written inline in the JS, and the page
   read as uneven because it *was* uneven. No rule below sets a raw font-size:
   every one names a step, and the JS uses the .u-* utilities rather than
   inventing its own.

   ── THE FONT ────────────────────────────────────────────────────────────
   San Francisco where it is legal, the closest thing to it everywhere else.

   Apple ships SF Pro free, but its licence limits use to "creating mock-ups
   of user interfaces to be used in software products running on Apple's iOS,
   iPadOS, macOS or tvOS" — a dashboard served from Cloudflare Pages is none
   of those, so the font file cannot be shipped. What IS allowed is asking the
   operating system for its own UI font, which on Apple hardware IS San
   Francisco: that is what -apple-system / BlinkMacSystemFont / system-ui do.

   So the stack asks for the real thing first and falls back to Inter, which
   was drawn as an SF-like UI face and is under the SIL Open Font License.
   The wall panel runs Windows, so in practice the wall sees Inter and anyone
   opening it on a Mac or an iPad sees genuine SF. Both share the same
   skeleton, so the layout does not shift between them.

   IBM Plex Sans Thai stays in the stack. The interface is English now, but
   salespeople's names come out of SAP in Thai and are data, not chrome —
   nobody's name gets transliterated to make a page look tidier. Apple's SF
   script extensions cover Arabic, Armenian, Georgian and Hebrew; there is no
   SF Thai, so Plex is the pairing.

   ── SIZING ──────────────────────────────────────────────────────────────
   The target is a 55" panel read from three or four metres, but a 55" 1080p
   TV reports the same 1920 CSS pixels as a laptop, so no media query can tell
   them apart. One knob does it: the root font-size, which every size here is
   expressed against. data-scale on <html> picks the setting; the choice is
   remembered per browser. */

:root {
  --bg-a: #f3f7ff;
  --bg-b: #e9f0fd;
  --card: #ffffff;
  --ink: #16224a;
  --ink-2: #2b3766;
  --ink-mid: #55628a;
  --ink-dim: #7d8aab;
  --hair: #e6ecf8;

  --blue:   #2f6bff;
  --green:  #12c07a;
  --orange: #ff9f1c;
  --purple: #a855f7;
  --pink:   #f2415f;
  --grey:   #98a4c0;

  --blue-s:   #e8efff;
  --green-s:  #e2f8f0;
  --orange-s: #fff3e0;
  --purple-s: #f5ebff;
  --pink-s:   #ffe9ee;
  --grey-s:   #eef1f7;

  /* One colour per sales channel, held across every tab the way the
     originals do: orange = Shop, violet = Online, green = Project. */
  --shop:    #ff8904;
  --online:  #6c4bff;
  --project: #16a34a;

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
          "Inter", "Segoe UI", system-ui,
          "IBM Plex Sans Thai", "Sarabun", sans-serif;

  /* The scale. Ratio ≈1.22 through the text sizes, then three numeral tiers.
     Add a step here before you ever write a font-size anywhere else. */
  --fs-micro: 0.70rem;   /* axis ticks, footers, source stamp            */
  --fs-tiny:  0.80rem;   /* labels, notes, table headers, legends        */
  --fs-small: 0.90rem;   /* table cells, secondary values                */
  --fs-body:  1.00rem;   /* tabs, selects, running text                  */
  --fs-title: 1.10rem;   /* card headings                                */
  --fs-h1:    1.75rem;   /* the page title, once                         */

  --fs-n3:    1.45rem;   /* numeral tier 3 — pipeline steps, work cards  */
  --fs-n2:    2.05rem;   /* numeral tier 2 — rows of four or five cards  */
  --fs-n1:    2.75rem;   /* numeral tier 1 — the headline figure         */

  /* Leading per step, not one multiplier. Apple specifies it per style and
     tightens it as size grows (11/13 = 1.18, 13/18 = 1.38, 34/41 = 1.21).
     Departure: their tightening is for Latin. Thai stacks a vowel above the
     letter and a tone mark above that, so the TEXT steps keep Thai-safe
     leading and only the numeral tiers — digits only — are tightened. */
  --lh-text: 1.45;
  --lh-title: 1.3;
  --lh-h1:   1.15;
  --lh-n3:   1.15;
  --lh-n2:   1.10;
  --lh-n1:   1.06;

  /* Optical sizing, approximated: the bigger the type, the tighter it is set.
     Small text gets a touch of positive tracking so it stays open at distance. */
  --tr-micro: .008em;
  --tr-tiny:  .006em;
  --tr-body:  0;
  --tr-title: -.006em;
  --tr-h1:    -.022em;
  --tr-n3:    -.014em;
  --tr-n2:    -.019em;
  --tr-n1:    -.026em;

  --r: .8rem;
  --shadow: 0 .12rem .55rem rgba(30, 55, 120, .08);
}

/* desk / laptop. The middle term is what makes the page grow and shrink with
   the window; an earlier version used 0.95vw, which at any normal window
   width sat BELOW the floor, so the page was pinned to the floor and resizing
   the browser changed nothing. */
html { font-size: clamp(16px, 1.25vw, 28px); -webkit-text-size-adjust: 100%; }
html[data-scale="wall"] { font-size: clamp(22px, 1.6vw, 38px); }
html[data-scale="huge"] { font-size: clamp(28px, 2.1vw, 50px); }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: linear-gradient(158deg, var(--bg-a) 0%, var(--bg-b) 100%);
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-text);
  -webkit-font-smoothing: antialiased;
}

/* Every figure on the page. Tabular figures keep columns aligned and stop a
   row of cards jittering as values change width. Tracking comes from the step
   the number is set at, not from here. */
/* Tabular by default: these are numbers in columns and numbers that tick in
   place, and both want fixed advance so nothing shuffles sideways when a digit
   changes. This is what SwiftUI's .monospacedDigit() is for. */
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.page { max-width: 2400px; margin: 0 auto; padding: .7rem 1rem .8rem; }

/* ---- type utilities --------------------------------------------------
   The JS renders card internals as strings. These exist so it can name a
   step instead of inventing a font-size, which is how the scale drifted. */
.u-micro { font-size: var(--fs-micro); letter-spacing: var(--tr-micro); }
.u-tiny  { font-size: var(--fs-tiny); letter-spacing: var(--tr-tiny); }
.u-small { font-size: var(--fs-small); }
.u-body  { font-size: var(--fs-body); }
.u-label { font-size: var(--fs-tiny); font-weight: 700; color: var(--ink-dim);
  letter-spacing: var(--tr-tiny); }
.u-note  { font-size: var(--fs-tiny); color: var(--ink-mid); letter-spacing: var(--tr-tiny); }
.u-n3 { font-size: var(--fs-n3); font-weight: 700; line-height: var(--lh-n3);
  letter-spacing: var(--tr-n3); white-space: nowrap; }
.u-n2 { font-size: var(--fs-n2); font-weight: 700; line-height: var(--lh-n2);
  letter-spacing: var(--tr-n2); white-space: nowrap; }
.u-n1 { font-size: var(--fs-n1); font-weight: 700; line-height: var(--lh-n1);
  letter-spacing: var(--tr-n1); white-space: nowrap; }

/* A row of inline metrics. Apple's rule for a number that outgrows its slot is
   to adjust the layout, not to shrink or clip the number — so these reflow to
   two columns, then one, instead of overprinting each other. The old fixed
   three-column grid is what printed "฿204.83M16.11M". */
.metrics {
  display: grid; gap: .45rem .9rem;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
}
.u-dim { color: var(--ink-dim); }
.u-mid { color: var(--ink-mid); }

/* ---- header ---------------------------------------------------------- */

.head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: .8rem; flex-wrap: wrap; margin-bottom: .7rem;
}
.head h1 { margin: 0; font-size: var(--fs-h1); font-weight: 700; color: var(--ink);
  letter-spacing: var(--tr-h1); line-height: var(--lh-h1); }
.head .subtitle {
  font-size: var(--fs-title); font-weight: 600; color: var(--blue);
  letter-spacing: var(--tr-title); line-height: var(--lh-title);
}
.headright { display: flex; align-items: stretch; gap: .5rem; flex-wrap: wrap; }
.stamp {
  background: var(--card); border-radius: .55rem; box-shadow: var(--shadow);
  padding: .35rem .7rem; font-size: var(--fs-micro); color: var(--ink-mid);
  text-align: right; max-width: 26rem;
}
.stamp b { color: var(--ink); font-weight: 600; }

.scaler { display: flex; align-items: center; gap: .2rem; background: var(--card);
  border-radius: .55rem; box-shadow: var(--shadow); padding: .3rem; }
.scaler button {
  border: 0; cursor: pointer; font-family: inherit; font-size: var(--fs-tiny);
  font-weight: 700; padding: .3rem .6rem; border-radius: .4rem;
  color: var(--ink-mid); background: transparent;
}
.scaler button[aria-pressed="true"] { background: var(--ink); color: #fff; }

/* ---- tabs ------------------------------------------------------------ */

.tabs { display: flex; gap: .35rem; flex-wrap: wrap; margin-bottom: .7rem; }
.tab {
  border: 0; cursor: pointer; font-family: inherit; font-size: var(--fs-body);
  font-weight: 600; padding: .45rem 1rem; border-radius: 999px;
  color: var(--ink-mid); background: rgba(255, 255, 255, .7); box-shadow: var(--shadow);
  transition: background .12s, color .12s;
}
.tab:hover { background: #fff; color: var(--ink); }
.tab[aria-selected="true"] { background: var(--ink); color: #fff; }

/* ---- filter bar ------------------------------------------------------ */

.filters {
  display: flex; gap: .45rem; flex-wrap: wrap; align-items: flex-end;
  background: var(--card); border-radius: var(--r); box-shadow: var(--shadow);
  padding: .5rem .7rem; margin-bottom: .6rem;
}
.f { display: flex; flex-direction: column; gap: .1rem; }
.f label { font-size: var(--fs-micro); color: var(--ink-dim); font-weight: 700; letter-spacing: .02em; }
.f select {
  font-family: inherit; font-size: var(--fs-small); color: var(--ink);
  border: 1px solid var(--hair); border-radius: .4rem; padding: .25rem .45rem;
  background: #fff; min-width: 7.5rem;
}
.f select:focus { outline: 2px solid var(--blue-s); border-color: var(--blue); }
.freset {
  align-self: center; border: 0; cursor: pointer; font-family: inherit;
  font-size: var(--fs-tiny); font-weight: 700; color: var(--ink-mid);
  background: var(--grey-s); border-radius: 999px; padding: .3rem .8rem;
}
.freset:hover { background: var(--blue-s); color: var(--blue); }

.views { display: flex; gap: .2rem; margin-left: auto; align-items: center;
  background: var(--grey-s); border-radius: 999px; padding: .18rem; }
.views button {
  border: 0; cursor: pointer; font-family: inherit; font-size: var(--fs-tiny);
  font-weight: 700; padding: .3rem .75rem; border-radius: 999px;
  color: var(--ink-mid); background: transparent; white-space: nowrap;
}
.views button[aria-pressed="true"] { background: var(--card); color: var(--ink); box-shadow: var(--shadow); }

/* ---- card ------------------------------------------------------------ */

.card {
  background: var(--card); border-radius: var(--r); box-shadow: var(--shadow);
  padding: .75rem .9rem;
}
.card > h2 {
  margin: 0 0 .5rem; font-size: var(--fs-title); font-weight: 700; color: var(--ink-2);
  line-height: var(--lh-title); letter-spacing: var(--tr-title);
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
}
.card > h2 .note { font-weight: 400; font-size: var(--fs-tiny); color: var(--ink-dim); }

.grid { display: grid; gap: .55rem; margin-bottom: .55rem; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.g6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
/* The platform breakdown row. Auto-fit rather than six fixed columns: on the
   55" setting the root font is ~31px and "฿639.03K" simply does not fit a
   sixth of the width. Apple's rule -- adjust the layout, do not shrink the
   number -- so the row wraps instead. */
.platrow { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.side { grid-template-columns: minmax(0, 1fr) 22rem; align-items: stretch; }
.side-l { grid-template-columns: 22rem minmax(0, 1fr); align-items: stretch; }

/* ---- KPI card --------------------------------------------------------
   Three numeral tiers and nothing between them. A row of cards picks ONE
   tier and every card in that row uses it, so the eye reads a row rather
   than a set of unrelated numbers. */

.kpi { display: flex; flex-direction: column; gap: .1rem; justify-content: center; }
.kpi .label {
  font-size: var(--fs-tiny); font-weight: 700; color: var(--ink-dim);
  text-transform: uppercase; letter-spacing: .04em; line-height: 1.3;
}
/* …but NOT the headline figure. Apple's note on San Francisco is explicit
   that "numbers have proportional widths by default, so they feel harmonious
   and naturally spaced", and it shows at display sizes: a tabular 1 sits in a
   slot as wide as an 8 and opens a visible gap beside it. Tabular is for the
   column; proportional is for the number you read as a headline. */
.kpi .value { font-variant-numeric: proportional-nums; font-feature-settings: "tnum" 0; }

.kpi .value {
  font-size: var(--fs-n1); font-weight: 700; line-height: var(--lh-n1);
  letter-spacing: var(--tr-n1); white-space: nowrap;
}
.kpi .value.t2 { font-size: var(--fs-n2); line-height: var(--lh-n2); letter-spacing: var(--tr-n2); }
.kpi .value.t3 { font-size: var(--fs-n3); line-height: var(--lh-n3); letter-spacing: var(--tr-n3); }
.kpi .foot { font-size: var(--fs-tiny); color: var(--ink-dim); line-height: 1.35; }

.t-blue { color: var(--blue); }     .t-green { color: var(--green); }
.t-orange { color: var(--orange); } .t-purple { color: var(--purple); }
.t-pink { color: var(--pink); }     .t-ink { color: var(--ink); }
.t-shop { color: var(--shop); }     .t-online { color: var(--online); }
.t-project { color: var(--project); }

/* The small "vs Last Month / Quarter / Year" rows Power BI stacks under a
   card value. A table so the three arrows line up. */
.refs { margin-top: .4rem; border-top: 1px solid var(--hair); padding-top: .3rem; }
.ref { display: flex; justify-content: space-between; gap: .5rem;
  font-size: var(--fs-tiny); padding: .05rem 0; }
.ref .rk { color: var(--ink-dim); }
.ref .rv { font-weight: 700; }
.up { color: var(--green); } .down { color: var(--pink); } .flat { color: var(--ink-dim); }

/* ---- pipeline strip -------------------------------------------------- */

/* auto-fit, not a fixed count: eleven steps squeezed into a narrow window gave
   each one ~54px, and "37,473" at the tier-3 size simply ran out of its cell.
   Apple's rule for a number that outgrows its slot is to change the layout, so
   the strip wraps to a second row instead of clipping. */
.pipe {
  display: grid; gap: .35rem;
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
}
.pipe .step {
  background: var(--card); border-radius: .6rem; box-shadow: var(--shadow);
  padding: .5rem .35rem; text-align: center;
}
/* Two lines, not an ellipsis. The eleven state names fitted on one line in
   Thai and do not in English — "Production Complete" needs 196px in a 140px
   cell — and clipping them to "Producti…" costs the reader the word that
   distinguishes it from "Production Order". Apple's rule for exactly this
   case: adjust the layout at larger text, keep truncation to a minimum. The
   fixed min-height keeps every card's number on the same baseline whether its
   own label wrapped or not. */
.pipe .step .nm {
  font-size: var(--fs-tiny); color: var(--ink-mid); font-weight: 600;
  line-height: 1.2; min-height: 2.4em; overflow-wrap: anywhere;
  display: flex; align-items: center; justify-content: center; text-align: center;
}
.pipe .step .n { font-size: var(--fs-n3); font-weight: 700; line-height: var(--lh-n3);
  letter-spacing: var(--tr-n3); }
.pipe .step .p { font-size: var(--fs-tiny); color: var(--ink-dim); }
.pipe .step .bar { height: 3px; border-radius: 3px; background: var(--hair);
  margin-top: .28rem; overflow: hidden; }
.pipe .step .bar i { display: block; height: 100%; }

/* ---- 100% stacked bar ------------------------------------------------ */

.stack { display: flex; height: 1.6rem; border-radius: .4rem; overflow: hidden; background: var(--grey-s); }
.stack > span {
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-tiny); font-weight: 700; color: #fff; min-width: 0;
}
.legend { display: flex; gap: .8rem; flex-wrap: wrap; margin-top: .35rem; font-size: var(--fs-tiny); }
.legend .k { display: flex; align-items: center; gap: .3rem; color: var(--ink-mid); }
.legend .k i { width: .55rem; height: .55rem; border-radius: 3px; display: inline-block; }

/* ---- progress -------------------------------------------------------- */

.track { height: .8rem; border-radius: 999px; background: var(--grey-s); overflow: hidden; }
.track i { display: block; height: 100%; border-radius: 999px; background: var(--blue); }
.trackrow { display: flex; justify-content: space-between; gap: .5rem;
  font-size: var(--fs-tiny); color: var(--ink-dim); margin-top: .25rem; }

/* ---- svg charts ------------------------------------------------------ */

.chart { width: 100%; display: block; }
.chart text { font-family: var(--font); }
.chart .cnum { font-variant-numeric: tabular-nums; }
/* A gauge is nearly square; stretched to the full width of a wide card it
   swallows a whole screen. Cap it and centre it instead. */
.chart.gauge { max-width: 22rem; margin: 0 auto; }

/* The donut used to be sized in hard pixels, which meant it ignored the
   scale knob entirely: at the 55" setting every other thing on the card grew
   and the chart stayed 110px, so it read as a dot next to its own legend. */
.donut { display: flex; align-items: center; gap: .7rem; }
.donut svg { flex: 0 0 auto; width: var(--donut, 7.5rem); height: var(--donut, 7.5rem); }
.donut .dlegend { font-size: var(--fs-tiny); color: var(--ink-mid); display: grid; gap: .1rem; }
.donut .dlegend i { width: .5rem; height: .5rem; border-radius: 2px; display: inline-block; margin-right: .3rem; }

/* ---- work-type block (Production · cards view) ----------------------- */

.work { display: grid; gap: .4rem; }
.work .whead { display: flex; align-items: baseline; justify-content: space-between; gap: .3rem; }
.work .whead .wn { font-size: var(--fs-small); font-weight: 700; }
.work .whead .wt { font-size: var(--fs-n2); font-weight: 700; line-height: var(--lh-n2);
  letter-spacing: var(--tr-n2); }
.work .due { display: grid; gap: .12rem; font-size: var(--fs-tiny); }
.work .due div { display: flex; justify-content: space-between; align-items: baseline; gap: .4rem; }
.work .due b { font-weight: 700; font-size: var(--fs-small); }

/* ---- matrix ---------------------------------------------------------- */

.matrix { width: 100%; border-collapse: separate; border-spacing: .18rem; font-size: var(--fs-small); }
.matrix th { font-size: var(--fs-tiny); color: var(--ink-dim); font-weight: 700; text-align: center; padding: .15rem; }
.matrix th.rowh { text-align: left; white-space: nowrap; }
.matrix td { text-align: center; border-radius: .35rem; padding: .4rem .2rem;
  font-weight: 700; font-size: var(--fs-body); font-variant-numeric: tabular-nums; }
.matrix td.zero { color: var(--ink-dim); background: var(--grey-s); font-weight: 400; }
/* The column the floor is judged on today. border-spacing keeps the cells
   apart, so a ring per cell reads as a marked column better than a border
   would — a border would break into dashes at every gap. */
.matrix .hl { box-shadow: 0 0 0 2px var(--pink); }
.matrix th.hl { color: var(--pink); box-shadow: none; text-decoration: underline;
  text-decoration-color: var(--pink); text-underline-offset: .25em; }

/* ---- table ----------------------------------------------------------- */

.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.tbl thead th {
  text-align: left; font-size: var(--fs-tiny); color: var(--ink-dim); font-weight: 700;
  border-bottom: 1px solid var(--hair); padding: .25rem .3rem; white-space: nowrap;
}
.tbl tbody td { padding: .24rem .3rem; border-bottom: 1px solid #f2f5fb; }
.tbl tbody tr:last-child td { border-bottom: 0; }
.tbl .r { text-align: right; }
.tbl .name { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tbl .bar { height: .45rem; border-radius: 999px; background: var(--grey-s); overflow: hidden; min-width: 3rem; }
.tbl .bar i { display: block; height: 100%; border-radius: 999px; }
.tbl tfoot td { border-top: 1px solid var(--hair); padding: .3rem; font-weight: 700; }
/* A subtotal row inside the body. Its own tinted band, so the eye can tell a
   channel subtotal from the platform rows indented beneath it without having
   to read the labels. */
.tbl tr.grp > * { background: #f5f7fc; font-weight: 700; border-bottom: 1px solid var(--hair); }
.tbl tr.grp .rowh { text-align: left; padding: .28rem .3rem; }
.scroll { max-height: 20rem; overflow-y: auto; }

/* ---- misc ------------------------------------------------------------ */

.footnote { font-size: var(--fs-tiny); color: var(--ink-dim); margin-top: .25rem; }
.loading { padding: 3rem; text-align: center; color: var(--ink-dim); }
.hidden { display: none !important; }
.err { background: #fff5f5; border: 1px solid #ffd0d0; border-radius: var(--r);
  padding: .6rem .8rem; font-size: var(--fs-small); color: #8a1f1f; }

/* Bottom-left corner. The stamp anchors the left end; the provenance notes
   fill the rest. */
.foot {
  margin-top: .8rem; font-size: var(--fs-micro); color: var(--ink-dim);
  display: flex; gap: .8rem; flex-wrap: wrap; align-items: flex-end;
}
.foot .stamp { text-align: left; flex: 0 0 auto; }
.foot .footnotes { flex: 1 1 20rem; display: grid; gap: .15rem; text-align: right; }

@media (max-width: 900px) {
  .foot .footnotes { text-align: left; }
}

/* ---- responsive ------------------------------------------------------ */

@media (max-width: 1400px) {
  .g5, .g6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .side, .side-l { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .g3, .g4, .g5, .g6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .g2, .g3, .g4, .g5 { grid-template-columns: minmax(0, 1fr); }
  .page { padding: .6rem .5rem 1.6rem; }
  .views { margin-left: 0; }
}

@media print {
  body { background: #fff; }
  .tabs, .filters, .scaler { display: none; }
  .card { break-inside: avoid; box-shadow: none; border: 1px solid var(--hair); }
}
