/* Pedro Moitinho — dashboard layer.
   EXTENDS css/portfolio.css; it does not fork it. Every surface, radius, blur
   and typographic value below resolves through the portfolio variables, so the
   two files cannot drift. The only new tokens are the three states this page
   needs and the portfolio does not: stale, up, down. They follow the same
   three-part theme pattern portfolio.css uses (bare :root, prefers-color-scheme
   guarded against an explicit light choice, and [data-theme="dark"]). */

:root {
    --stale-fg: #8a5000;
    --stale-bg: rgba(255, 149, 0, 0.12);
    --stale-line: rgba(160, 95, 0, 0.45);
    --up-fg: #14743a;
    --down-fg: #a52018;
    --neutral-fg: var(--text-secondary);
    --info-fg: #12518f;
    --chart-line: var(--accent);
    --chart-fill: rgba(0, 122, 255, 0.14);
    --bar-fill: rgba(0, 122, 255, 0.20);
    --rule: rgba(0, 0, 0, 0.06);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --stale-fg: #ffb340;
        --stale-bg: rgba(255, 159, 10, 0.16);
        --stale-line: rgba(255, 179, 64, 0.55);
        --info-fg: #6cb6ff;
        --up-fg: #41d26a;
        --down-fg: #ff6961;
        --chart-fill: rgba(10, 132, 255, 0.22);
        --bar-fill: rgba(10, 132, 255, 0.28);
        --rule: rgba(255, 255, 255, 0.07);
    }
}

:root[data-theme="dark"] {
    --stale-fg: #ffb340;
    --stale-bg: rgba(255, 159, 10, 0.16);
    --stale-line: rgba(255, 179, 64, 0.55);
    --info-fg: #6cb6ff;
    --up-fg: #41d26a;
    --down-fg: #ff6961;
    --chart-fill: rgba(10, 132, 255, 0.22);
    --bar-fill: rgba(10, 132, 255, 0.28);
    --rule: rgba(255, 255, 255, 0.07);
}

/* A shared surface: the same glass the portfolio cards use, without the hover
   lift — nothing on this page is clickable just because it is a panel. */
.panel {
    background: var(--bg-card);
    -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 22px;
}

.tnum { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* --- Freshness line --------------------------------------------------- */
/* v1.5: one small line at the foot of the page, not a banner. The notice at
   the top still speaks up when a run is missed. */
.freshness {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
    margin-top: 28px;
    font-size: 12.5px; color: var(--text-secondary);
}
.freshness .stamp { font-weight: 500; }
.freshness.is-error, .freshness.is-error .stamp { color: var(--stale-fg); }
.freshness.is-warn .stamp { color: var(--stale-fg); }

/* Caution-tape rail. The hatch is the non-colour half of the stale signal;
   keeping it off the text means it can be strong without costing legibility. */
.hatched { position: relative; padding-left: 20px; }
.hatched::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 7px;
    border-radius: inherit; border-top-right-radius: 0; border-bottom-right-radius: 0;
    background: repeating-linear-gradient(135deg, var(--stale-fg) 0 3px, transparent 3px 7px);
    opacity: 0.85;
}

/* Stale is never colour alone: word + glyph + hatch + dotted rule. */
.badge {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    border-radius: 100px; padding: 2px 9px;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    white-space: nowrap;
}
.badge-stale { color: var(--stale-fg); border-color: var(--stale-line); background: var(--stale-bg); }
.badge-live { color: var(--up-fg); border-color: currentColor; }
.badge-review { color: var(--info-fg); border-color: currentColor; }
.badge-glyph { font-size: 12px; line-height: 1; }

/* --- App cards --------------------------------------------------------- */
.app-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.app-card { display: flex; flex-direction: column; }
.app-head { display: flex; align-items: flex-start; gap: 12px; }
.app-mark {
    position: relative; overflow: hidden;
    width: 40px; height: 40px; border-radius: 11px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 15px; font-weight: 700; letter-spacing: -0.02em;
    color: var(--accent-contrast);
    background: linear-gradient(135deg, #5c5c61, #8e8e93);
}
/* Artwork covers the monogram rather than replacing it: the initials are still
   in the tile if the file is missing, and still the whole tile for an app that
   has no icon here at all. Same footprint, same radius, same alignment. */
.app-mark img {
    position: absolute; inset: 0;
    width: 100%; height: 100%; object-fit: cover; display: block;
}
/* App Store artwork is square, edge-to-edge and often pale at the border, so
   the tile needs an edge of its own — otherwise a light icon dissolves into a
   light card, and a dark one into a dark card. Drawn only over real artwork;
   the gradient monograms already have contrast against both themes. */
.app-mark.has-art::after {
    content: ''; position: absolute; inset: 0;
    border-radius: inherit;
    box-shadow: inset 0 0 0 1px var(--icon-edge);
    pointer-events: none;
}
.app-mark[data-app="ccb"]                { background: linear-gradient(135deg, #1f4f8f, #2f7fd0); }
.app-mark[data-app="library"]            { background: linear-gradient(135deg, #6b5b4a, #8a7a68); }
.app-mark[data-app="meditations"]        { background: linear-gradient(135deg, #4a4f6b, #6d7395); }
.app-mark[data-app="art-of-war"]         { background: linear-gradient(135deg, #6b2f2f, #96504a); }
.app-mark[data-app="the-prince"]         { background: linear-gradient(135deg, #4b3a63, #6f5a8c); }
.app-mark[data-app="book-of-five-rings"] { background: linear-gradient(135deg, #2f5b4a, #4d8168); }
.app-mark[data-app="de-brevitate-vitae"]  { background: linear-gradient(135deg, #5c4a2f, #8a7145); }
.app-name { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; }
.app-platforms { font-size: 12.5px; color: var(--text-secondary); margin-top: 2px; }

/* The platform names are the way into the stores. Underlined on hover only:
   in the card head they are identity first and navigation second. */
.store-link { color: inherit; text-decoration: underline; text-underline-offset: 2px;
    text-decoration-thickness: 1px; text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); }
.store-link:hover, .store-link:focus-visible { text-decoration-color: currentColor; }

.metric { margin-top: 16px; }
/* The stale mark sits against the number it qualifies — see dashboard.html.
   Baseline-aligned so the badge reads as an annotation on the figure rather
   than as a second element competing with it. */
.metric-value-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }

.metric-value {
    font-size: 27px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1;
    color: var(--text-primary);
}
.metric-label { font-size: 12.5px; color: var(--text-secondary); }
.metric-asof {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    font-size: 12.5px; color: var(--text-secondary); margin-top: 6px;
}
.metric.is-stale .metric-value {
    text-decoration: underline dotted var(--stale-fg);
    text-underline-offset: 6px;
    text-decoration-thickness: 2px;
}
.metric.is-stale .metric-asof { color: var(--stale-fg); }
/* --- Sparkline --------------------------------------------------------- */
.spark { margin-top: 14px; }
.spark svg { display: block; width: 100%; height: 46px; overflow: visible; }
.spark-line { fill: none; stroke: var(--chart-line); stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
.spark-area { fill: var(--chart-fill); stroke: none; }
.spark-tick { stroke: var(--chart-line); stroke-width: 2; }
.spark-base { stroke: var(--rule); stroke-width: 1; }
/* The readout is the chart's non-visual twin: every fact the line carries is
   also here as text, so the shape is never the only way to read it. */
.spark-read {
    display: flex; flex-wrap: wrap; gap: 4px 14px;
    font-size: 12.5px; color: var(--text-secondary); margin-top: 7px;
}
.spark-read b { color: var(--text-primary); font-weight: 600; }

/* Pinned to the foot of the card so the release line sits on one baseline
   across the grid, however tall the card above it happens to be. */
.app-version {
    display: flex; flex-wrap: wrap; align-items: center; gap: 7px 9px;
    margin-top: auto; padding-top: 14px; border-top: 1px solid var(--rule);
    font-size: 13px; color: var(--text-secondary);
}
.app-version > .spacer { flex-basis: 100%; height: 0; }
.app-version .ver { color: var(--text-primary); font-weight: 600; }
.adoption { display: flex; flex-wrap: wrap; align-items: center; gap: 7px 9px; width: 100%; }
.adoption-track {
    flex: 1 1 60px; height: 6px; border-radius: 100px; overflow: hidden;
    background: var(--rule); border: 1px solid var(--border-color);
}
.adoption-fill { height: 100%; background: var(--accent); border-radius: 100px; }
/* A published share always arrives with its denominator; the phrase wraps as
   one unit rather than breaking across "250 sampled / iOS devices". */
.adoption-denom { white-space: nowrap; }

/* The last line of the card, and there is always exactly one of it: it holds
   the adoption bar where a share is published and the withheld phrase where it
   is not. Those two cases are mutually exclusive by construction, so this row
   is never both and never neither.

   `flex-basis: 100%` is what makes the two interchangeable — the bar inside is
   `width: 100%` and needs the whole line, and the phrase is happy on a line of
   its own. `min-height` is the part that holds the grid together: it pins the
   row to one text line so a card's footer sits at the same y as its
   neighbour's, which is the whole point of putting them in one slot.

   A withheld share costs one phrase on the line the card already had — no
   dedicated slot. With five of six apps below the floor indefinitely, a
   reserved empty row would be permanent furniture. The two withheld cases are
   told apart by their words and by a glyph; neither quotes a device count. */
.ver-foot {
    display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
    flex-basis: 100%; min-height: 21px;
}
.ver-foot-glyph { font-size: 11px; line-height: 1; color: var(--text-secondary); }

/* The one line the cards do not repeat. A rule down the left marks it as a
   statement rather than fine print, without the alarm of the amber rail —
   nothing here is wrong, it is a measurement declining to overclaim. */
.note-block {
    margin-top: 16px;
    padding: 2px 0 2px 16px;
    border-left: 2px solid var(--text-tertiary);
    max-width: 68ch;
}
.note-block[hidden] { display: none; }
.note-body-text { font-size: 13px; color: var(--text-secondary); line-height: 1.6; }
.note-body-text strong { color: var(--text-primary); font-weight: 600; }

/* --- Library usage (v1.5) ---------------------------------------------- */
/* Four figures in a strip, then the table by book, in one panel — the same
   record-then-list shape the requests panel uses. */
.use-window { margin-top: 0; }
.use-strip {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 22px;
    margin-top: 14px; padding-bottom: 18px; border-bottom: 1px solid var(--rule);
}
.use-fig { min-width: 0; }
.use-num { font-size: 27px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: var(--text-primary); }
.use-cap { font-size: 12.5px; color: var(--text-secondary); margin-top: 3px; line-height: 1.35; }
.use-delta { margin-top: 6px; font-size: 12.5px; }
.use-strip[hidden] { display: none; }
.sr-only {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.usage .table-wrap { margin-top: 16px; }

/* --- Books table -------------------------------------------------------- */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* Only the stacked-card layout folds its tail away, so the control only exists
   there. Above 620px the table shows every row and the button is never drawn. */
/* ⚠ Visible at every width. This was `display: none` here and only flipped to
   inline-flex inside the 620px query, the other half of the mobile-only
   collapse: on a desktop the page rendered all 25 rows AND hid the button that
   was supposed to reveal them. */
.more-btn { display: inline-flex; margin-top: 14px; }
.more-btn[hidden] { display: none; }
table.books { width: 100%; border-collapse: collapse; font-size: 14px; }
table.books caption { text-align: left; font-size: 13px; color: var(--text-secondary); padding-bottom: 12px; }
table.books th, table.books td { padding: 10px 12px; text-align: left; vertical-align: middle; }
table.books thead th {
    font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-secondary); border-bottom: 1px solid var(--border-color); white-space: nowrap;
}
table.books tbody tr { border-bottom: 1px solid var(--rule); }
table.books tbody tr:last-child { border-bottom: 0; }
.col-rank { width: 40px; color: var(--text-secondary); }
/* Specificity has to beat `table.books td`, which sets text-align: left. */
table.books th.col-num,
table.books td.col-num { text-align: right; white-space: nowrap; }
.bk-title { font-weight: 500; color: var(--text-primary); }
/* A fixed track, right-aligned under the right-aligned number, so the fill
   reads left-to-right and still hangs off the figure it belongs to. It is
   decoration for that number, never a substitute for it. */
.bar {
    position: relative; display: block; height: 4px; width: 88px; max-width: 100%;
    margin: 6px 0 0 auto; border-radius: 100px; background: var(--rule);
}
.bar > i { position: absolute; inset: 0 auto 0 0; border-radius: 100px; background: var(--bar-fill); border-right: 2px solid var(--chart-line); }
.trend { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.trend-up { color: var(--up-fg); }
.trend-down { color: var(--down-fg); }
.trend-flat, .trend-none { color: var(--neutral-fg); }
.trend .arrow { font-size: 11px; line-height: 1; }

/* --- Two-up panels ----------------------------------------------------- */
/* The requests panel. One card carrying the record and the queue, because the
   two-card version could not be balanced: two numbers on the left against five
   rows on the right, forced to equal height, left a hole the old CSS pushed the
   figures to the floor to disguise.

   The record is a strip, not a column: two figures reading left to right with
   the date pushed to the far end, so it occupies a band rather than a half. The
   queue then gets the full width underneath, which is also what lets each row
   put its author and language on the right instead of stacking them. */
.requests .req-record {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 18px 36px;
    margin-top: 18px; padding-bottom: 18px; border-bottom: 1px solid var(--rule);
}
/* Pushed to the end of the strip on a wide card; on a wrapped one it simply
   becomes the last line, which is where it would have gone anyway. */
.requests .req-record > .metric-asof { margin-left: auto; margin-top: 0; }
.requests .req-next-head {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    margin-top: 18px;
}
.requests .req-next-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.requests .req-next-sub { font-size: 13px; color: var(--text-secondary); }
.requests > .footnote { margin-top: 14px; }
.pipeline-figs { display: flex; flex-wrap: wrap; gap: 20px 32px; margin-top: 4px; }
.pipeline-num { font-size: 30px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.05; }
.pipeline-cap { font-size: 13px; color: var(--text-secondary); margin-top: 3px; }
.panel-title { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.panel-sub { font-size: 13px; color: var(--text-secondary); margin-top: 4px; }

.queue { list-style: none; margin-top: 14px; display: grid; gap: 2px; }
.queue li { display: flex; align-items: baseline; gap: 11px; padding: 8px 0; border-top: 1px solid var(--rule); }
/* Full width earns this: title on the left, author and language on the right,
   one row per book instead of two stacked lines. `min-width: 0` so a long
   title ellipsises rather than shoving the meta off the card. It reverts to
   stacked below the mobile breakpoint, where there is no width to spend. */
.queue li > div { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap;
                  align-items: baseline; justify-content: space-between; gap: 2px 14px; }
.queue li > div > .q-meta { margin-top: 0; text-align: right; }
.queue li:first-child { border-top: 0; }
.queue .q-rank {
    flex: 0 0 22px; font-size: 12px; font-weight: 700; color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}
.queue .q-title { font-size: 14px; font-weight: 500; }
.queue .q-meta { font-size: 12.5px; color: var(--text-secondary); margin-top: 1px; }

/* --- Empty / error states --------------------------------------------- */
/* Nothing here ever invents a number. When the payload is missing, every slot
   collapses to an em dash and the page says so, once, loudly. */
.notice {
    display: flex; gap: 13px; align-items: flex-start;
    border-radius: var(--radius); padding: 18px 20px 18px 26px; margin-top: 24px;
    border: 1px solid var(--stale-line);
    background: linear-gradient(var(--stale-bg), var(--stale-bg)), var(--bg-card);
    -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
}
.notice-glyph { font-size: 17px; line-height: 1.4; color: var(--stale-fg); flex-shrink: 0; }
.notice-title { font-weight: 600; font-size: 14.5px; color: var(--text-primary); }
.notice-body { font-size: 13.5px; color: var(--text-secondary); margin-top: 4px; line-height: 1.55; }
.notice[hidden] { display: none; }

.empty-row { padding: 22px 12px; text-align: center; color: var(--text-secondary); font-size: 13.5px; }
table.books td.empty-row { text-align: center; }
.queue li.empty-row { display: block; border: 0; }
.is-placeholder .use-num,
.is-placeholder .metric-value,
.is-placeholder .pipeline-num { color: var(--text-tertiary); }

.footnote { font-size: 13px; color: var(--text-secondary); margin-top: 14px; line-height: 1.55; }

/* Focus: the site never styled one, and a data page is full of controls. */
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 6px;
}

/* --- Narrow ------------------------------------------------------------ */
/* ⚠ The collapse once lived INSIDE the 620px media query, so it only ever
   worked on a phone: at any wider width all 25 rows rendered while the button
   underneath still offered to "Show all 25". Top-level, because the list
   being long is not a mobile problem. */
table.books.is-collapsed tbody tr.bk-extra { display: none; }

@media (max-width: 720px) {
    .app-grid { grid-template-columns: 1fr; }
    .use-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* No width to spend: the queue row goes back to two stacked lines, and
       the record's date stops being pushed to a far edge that no longer exists. */
    .queue li > div { display: block; }
    .queue li > div > .q-meta { margin-top: 1px; text-align: left; }
    .requests .req-record > .metric-asof { margin-left: 0; flex-basis: 100%; }
}

@media (max-width: 620px) {
    .panel { padding: 18px; }

    /* The table stops being a grid and becomes one block per book. Real
       <table> semantics stay intact for screen readers; only the visual box
       changes, and every cell keeps its column name via data-label. */
    table.books, table.books tbody, table.books tr, table.books td { display: block; width: 100%; }
    /* A table-caption inside a display:block table shrink-to-fits into a sliver;
       it has to become a block of its own. */
    table.books caption { display: block; }
    table.books thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
    table.books tbody tr {
        border: 1px solid var(--border-color); border-radius: var(--radius-sm);
        padding: 12px 14px; margin-bottom: 10px; background: var(--bg-card);
    }
    table.books td { padding: 0; border: 0; }
    table.books td.col-rank {
        width: auto; font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em;
        text-transform: uppercase; margin-bottom: 4px;
    }
    table.books td.col-rank::before { content: "#"; }
    table.books td[data-label] { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; margin-top: 8px; }
    table.books td[data-label]::before {
        content: attr(data-label);
        font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
        color: var(--text-secondary); flex-shrink: 0;
    }
    table.books td.col-title { margin-top: 0; }
    table.books td.col-title::before { content: none; }
    table.books td.col-num { text-align: right; }
    table.books td .bar { flex-basis: 100%; }
    .bar { display: none; }
}
