/* /Layout/MainLayout.razor.rz.scp.css */
/*  THE SAME LAYOUT AS ControlsInventory\Layout\MainLayout.razor.css AND
    ProjectHub's, rule for rule, with --sp-* where those files have --ci-* and
    --ph-*. The three apps match by construction rather than by measurement -
    which is what the 2026-08-31 change bought, and is why this file is a copy
    with three renamed variables rather than a fresh piece of design.

    docs\DESIGN-BASELINE.md is the record; if this file disagrees with that one,
    this file is wrong.

    Values inherited from that baseline:
      3.5rem   bar height
      1px      hairline under it, in --sp-line
      1.05rem / 600 / 0.01em   the app name */

.app[b-80iyjkvzbp] {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background-color: var(--sp-void);
}

/*  ⚠ THE LEFT PADDING IS CLEARANCE, NOT RHYTHM - do not normalise it to match the
    other sides.

    SiteMenu's burger is position:fixed at the top left: a 44px button on a 9px
    inset, so it occupies the first 53px of the viewport in both axes. It is fixed,
    which means it is out of flow and this bar knows nothing about it - nothing here
    can be pushed aside by it. 3.875rem (62px) puts the app name clear of it with
    8px of air. */
.app-bar[b-80iyjkvzbp] {
    display: flex;
    align-items: center;
    min-height: 3.5rem;
    padding-left: 3.875rem;
    padding-right: 1rem;
    border-bottom: 1px solid var(--sp-line);
}

.app-name[b-80iyjkvzbp] {
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--sp-ink);
}

/*  1.5rem over, 3rem under, 1.5rem each side, at every width.

    ⚠ min-width: 0 MATTERS MORE IN THIS APP THAN IN THE OTHERS. A flex item
    defaults to min-width:auto, so a child wider than the viewport stretches the
    layout instead of scrolling inside itself. In Controls Inventory the child
    that can do that is a wide table. Here it is the drawing canvas, which is
    sized from an aspect ratio and would otherwise be free to push the whole app
    onto a horizontal scrollbar on a phone. */
main[b-80iyjkvzbp] {
    flex: 1;
    min-width: 0;
    padding: 1.5rem 1.5rem 3rem;
}
/* /Layout/SiteMenu.razor.rz.scp.css */
/* The menu — a port of the hamburger in the website's section of /css/studio.css,
   scoped to this component so it cannot collide with Bootstrap or with the app's
   own chrome.

   IDENTICAL COPY in the other two apps' Layout\SiteMenu.razor.css. See the note at
   the top of SiteMenu.razor for why there are three.

   The palette is declared here rather than read from the host app's tokens, so this
   component looks the same in every app by construction. Same six values as
   section 1 of studio.css. */
.site-menu-root[b-pvxv6v1f5v] {
    --sm-void: #0c1118;
    --sm-surface: #172230;
    --sm-raised: #202c3c;
    --sm-line: #253141;
    --sm-ink: #e3e9f1;
    --sm-dim: #8a9bb1;
    --sm-signal: #4d9bff;
}

/*  NAV CHROME GEOMETRY - literal lengths, matching the website exactly so the
    button does not move when you cross from a page into an app. The numbers, so
    they can be changed coherently:
      18px   inset from the corner
      44px   button box: the smallest touch target a fingertip hits reliably
      26px   visible icon, centred in that box
       9px   button offset = 18 - (44 - 26) / 2, so the ICON lands on 18px
      57px   panel top = 9 + 44 + 4

    ⚠ LEFT, NOT RIGHT, AND THE APPS DEPEND ON IT. This used to be the right-hand
    corner, opposite each app's own rail hamburger on the left. The rails are gone
    and this is the only menu button on the domain, so it took the left corner they
    vacated. Every app's top bar reserves 3.875rem of left padding for it (9 + 44 +
    8 = 61px) - a bar that stops reserving it puts its own title under this button.
    The three static pages reserve nothing because they have no bar; on /content/
    the corner lockup moved to the RIGHT to get out of the way.

    Z-INDEX sits above Bootstrap's fixed layer (1030) and below its modal layer
    (1055). The website's copy can use 3; inside an app there is a whole component
    library underneath that has opinions about stacking. */
.burger[b-pvxv6v1f5v] {
    position: fixed;
    top: 0.5625rem;    /* 9px */
    left: 0.5625rem;
    width: 2.75rem;    /* 44px touch target */
    height: 2.75rem;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    z-index: 1042;
}

.bars[b-pvxv6v1f5v] {
    position: relative;
    display: block;
    width: 1.625rem;
    height: 1.25rem;
}

.bars i[b-pvxv6v1f5v] {
    position: absolute;
    left: 0;
    width: 100%;
    height: 3px;
    /* Half the bar height, so each `top` centres its bar rather than hanging it. */
    margin-top: -1.5px;
    border-radius: 1.5px;
    background: var(--sm-ink);
}

.bars i:nth-child(1)[b-pvxv6v1f5v] { top: 10%; }
.bars i:nth-child(2)[b-pvxv6v1f5v] { top: 50%; }
.bars i:nth-child(3)[b-pvxv6v1f5v] { top: 90%; }

.bars[b-pvxv6v1f5v], .bars i[b-pvxv6v1f5v] {
    transition: transform 320ms cubic-bezier(.2, .8, .2, 1), opacity 160ms ease;
}

/* Opening turns the box a quarter turn while the outer bars swing to 45deg. A
   quarter turn lands the X back at 45deg either way, so the end state is still the
   universal close affordance. */
.burger[aria-expanded="true"] .bars[b-pvxv6v1f5v] { transform: rotate(90deg); }
.burger[aria-expanded="true"] .bars i:nth-child(1)[b-pvxv6v1f5v] { top: 50%; transform: rotate(45deg); }
.burger[aria-expanded="true"] .bars i:nth-child(2)[b-pvxv6v1f5v] { opacity: 0; transform: scaleX(0.2); }
.burger[aria-expanded="true"] .bars i:nth-child(3)[b-pvxv6v1f5v] { top: 50%; transform: rotate(-45deg); }

.burger:focus-visible[b-pvxv6v1f5v] { outline: 1px solid var(--sm-signal); outline-offset: 0.25rem; }

.scrim[b-pvxv6v1f5v] {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(12, 17, 24, 0.8);
    opacity: 0;
    visibility: hidden;
    transition: opacity 200ms ease, visibility 200ms;
}

.scrim.open[b-pvxv6v1f5v] { opacity: 1; visibility: visible; }

.menu[b-pvxv6v1f5v] {
    position: fixed;
    top: 3.5625rem;   /* 57px - just under the button box */
    left: 1.125rem;   /* 18px - same inset as the button's icon */
    z-index: 1041;
    display: flex;
    flex-direction: column;
    min-width: 13.5rem;

    /*  THE PANEL OUTGROWS A SHORT WINDOW, so it is allowed to scroll. Absorbing
        both rails took it from 5 rows to 13 on 2026-08-31 - about 533px - and
        Scratchpad took it to 17, about 700px. That is taller than a phone in
        portrait, so the scroll is now load-bearing on the most common device
        rather than only on a short laptop window. Without it the last rows would
        be unreachable off the bottom of the viewport with no indication they were
        there.

        `hidden auto` rather than plain `auto`: horizontal stays clipped, so the
        rounded corners keep clipping the first and last rows. */
    max-height: calc(100vh - 5rem);
    overflow: hidden auto;

    background: var(--sm-surface);
    border: 1px solid var(--sm-line);
    border-radius: 6px;
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.5);
    /* visibility, not display: it can transition, and a visibility:hidden subtree
       is untabbable without needing inert or tabindex juggling. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.35rem);
    transition: opacity 160ms ease, transform 260ms cubic-bezier(.2, .8, .2, 1), visibility 260ms;
}

.menu.open[b-pvxv6v1f5v] { opacity: 1; visibility: visible; transform: none; }

/*  ⚠ EVERY ROW RULE BELOW IS `.menu ::deep`, AND IT IS REQUIRED, NOT TIDINESS.

    The third-level rows - each app's own screens, inside the app that is running -
    are <NavLink> COMPONENTS. Blazor stamps this sheet's scope attribute only onto
    elements written in THIS file's markup, so the <a> a NavLink renders never gets
    it: a plain `.item { }` rule compiles to `.item[b-xxxxxxxxxx]` and matches
    nothing at all for those rows. They would render as unstyled blue underlined
    links in the middle of a styled panel, with no error anywhere.

    ::deep drops the attribute off the last part of the selector and puts it on the
    ancestor instead - `.menu[b-xxxxxxxxxx] .item` - which matches BOTH kinds of row,
    the component-rendered ones and the anchors and spans written here, because all
    of them are inside .menu. So the whole block goes through it rather than half. */
.menu[b-pvxv6v1f5v]  .item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.25rem;
    padding: 0.7rem 0.9rem;
    border-left: 3px solid transparent;
    text-decoration: none;
    color: var(--sm-ink);
    font-weight: 600;
    font-size: 0.95rem;
    white-space: nowrap;
    transition: background 120ms ease, border-left-color 120ms ease;
}

.menu[b-pvxv6v1f5v]  .item + .item { border-top: 1px solid var(--sm-line); }

.menu[b-pvxv6v1f5v]  a.item:hover,
.menu[b-pvxv6v1f5v]  a.item:focus-visible {
    background: var(--sm-raised);
    border-left-color: var(--sm-signal);
    color: var(--sm-ink);
    outline: none;
}

/* The page you are already on: still listed, so the nav does not change shape from
   page to page, but not a link and not competing for attention. */
.menu[b-pvxv6v1f5v]  .item.current { color: var(--sm-dim); font-weight: 400; }

/* An application, which lives inside /backend/ rather than beside it. Indented by
   the difference so the LABEL steps in while the border-left marker stays on the
   same vertical line as every other item. */
.menu[b-pvxv6v1f5v]  .item.sub { padding-left: 1.9rem; }

/*  A screen INSIDE an application - the third level, and what used to be that app's
    rail. One more step of indent, and lighter than the two levels above it: these
    are the most numerous rows in the panel and reading them as a block under their
    app matters more than reading each one as a destination in its own right. */
.menu[b-pvxv6v1f5v]  .item.screen {
    padding-left: 2.9rem;
    font-weight: 400;
    font-size: 0.9rem;
    color: var(--sm-dim);
}

/*  THE SIGNATURE DEVICE, INHERITED FROM THE RAILS THIS MENU REPLACED: the screen
    you are on is edge-lit in the signal colour and lifted to the same value as a
    card. "active" is NavLink's own class, set from the current route rather than
    from anything this component tracks, which is the whole reason those rows are
    NavLinks and not anchors.

    It has to out-specify .item.screen's colour above, which is why the class is
    doubled up in the selector rather than relying on source order. */
.menu[b-pvxv6v1f5v]  .item.screen.active {
    background: var(--sm-raised);
    border-left-color: var(--sm-signal);
    color: #ffffff;
    font-weight: 600;
}

.menu[b-pvxv6v1f5v]  .tag {
    display: inline-block;
    font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.1rem 0.4rem;
    border-radius: 3px;
    border: 1px solid var(--sm-line);
    color: var(--sm-dim);
    margin-left: 0.5rem;
    vertical-align: 0.1em;
}

@media (prefers-reduced-motion: reduce) {
    .bars[b-pvxv6v1f5v], .bars i[b-pvxv6v1f5v], .scrim[b-pvxv6v1f5v], .menu[b-pvxv6v1f5v] { transition: none; }
    .menu[b-pvxv6v1f5v]  .item { transition: none; }
}
/* /Pages/Dashboard.razor.rz.scp.css */
/* A stat tile. One clear step above the page field, like every other panel in the
   app, with the figure carrying the weight and the label and note kept quiet
   around it. Matched to the other two dashboards' tiles: same field, same border,
   same uppercase label over a large figure. */
.tile[b-eydu7l0xap] {
    height: 100%;
    padding: 0.85rem 1rem 0.9rem;
    background: var(--sp-surface);
    border: 1px solid var(--sp-line);
    border-radius: 8px;
}

.tile-label[b-eydu7l0xap] {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--sp-dim);
}

.tile-figure[b-eydu7l0xap] {
    font-size: 1.75rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--sp-ink);
    /* Tabular figures so the four tiles' numbers line up on their digits rather
       than drifting with the proportional font. */
    font-variant-numeric: tabular-nums;
}

/* The caveat under a figure - what it counts, or what is missing from it. Small
   and dim on purpose: it must be readable when you go looking for it and must not
   compete with the number. */
.tile-note[b-eydu7l0xap] {
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--sp-dim);
}

/*  ⚠ NO .mono RULE HERE, ON PURPOSE. Canvas dimensions are a measurement rather
    than prose and want the monospace treatment, but studio.css section 3 already
    defines `.font-monospace` globally with exactly that - the app font's mono
    stack and the cool tint that makes an identifier scan. Both list screens use
    that class. Controls Inventory's dashboard carries a local `.mono` predating
    it; copying that here would be a second definition of one idea, scoped to one
    page, that only ever diverges.

    The opening of a note, in the recent list.

    ⚠ THE WIDTH CAP IS WHAT KEEPS THIS TABLE READABLE. An excerpt is up to 160
    characters on one line; without a bound it takes as much width as it wants and
    squeezes the three columns that carry the actual facts down to nothing. The
    ellipsis is the browser's, so the cut lands wherever the column happens to end
    rather than at a character count - which is right, because the column width is
    what is actually running out. */
.excerpt[b-eydu7l0xap] {
    max-width: 30rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* /Pages/DrawingEditor.razor.rz.scp.css */
/*  The drawing editor. Three pieces: a header row across the top, then a tool
    panel and the canvas side by side. */

/* Wide enough for a real title and no wider - it sits in a flex row with the save
   button and the status, and a full-width input would push both off a phone. */
.title-box[b-8ehzsgdyag] {
    max-width: 22rem;
    flex: 1 1 12rem;
}

/* "Saved 2026-09-01 14:32" or "Unsaved changes". Quiet until it has something to
   say, then the signal colour - this is the only indication that a canvas holds
   work the server does not have. */
.status[b-8ehzsgdyag] {
    font-size: 0.85rem;
    color: var(--sp-dim);
    white-space: nowrap;
}

.status.unsaved[b-8ehzsgdyag] {
    color: var(--sp-signal);
    font-weight: 600;
}

/*  ---------------------------------------------------------------------------
    THE TOOL PANEL

    A row of the panel is FOUR CELLS OF A FIXED SIZE, at every width. Not four
    fractions of the panel: the panel wraps above the canvas on a narrow screen,
    where `repeat(4, 1fr)` would stretch each cell to a quarter of the viewport
    and give four 5rem buttons with an icon marooned in the middle of each.

    Sized so the whole thing settles at about 14rem, which is the width the six
    colour swatches want anyway.  */
.studio[b-8ehzsgdyag] {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 1rem;
}

.toolbox[b-8ehzsgdyag] {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    padding: 0.7rem;
    background: var(--sp-surface);
    border: 1px solid var(--sp-line);
    border-radius: 8px;
}

.tool-grid[b-8ehzsgdyag] {
    display: grid;
    grid-template-columns: repeat(4, 2.9rem);
    gap: 0.3rem;
}

.tool-label[b-8ehzsgdyag] {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--sp-dim);
    margin-bottom: 0.35rem;
}

/* The stroke count. Sits under the tools rather than beside the canvas because
   it is the one number that answers "did that mark register" - which is a
   question about the pen, not about the page. */
.tool-foot[b-8ehzsgdyag] {
    border-top: 1px solid var(--sp-line);
    padding-top: 0.5rem;
    font-size: 0.8rem;
}

/*  A tool cell: icon over a caption, in a square. 2.9rem is 46px, which clears
    the 44px touch target - the old inline toolbar could not afford that and said
    so; a column can. */
.tool-btn[b-8ehzsgdyag] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.12rem;
    width: 100%;
    aspect-ratio: 1 / 1;
    padding: 0;
    border: 1px solid var(--sp-line);
    border-radius: 6px;
    background: var(--sp-raised);
    color: var(--sp-ink);
    cursor: pointer;
}

.tool-btn:hover:not(:disabled)[b-8ehzsgdyag] { border-color: var(--sp-dim); }

.tool-btn:disabled[b-8ehzsgdyag] {
    opacity: 0.4;
    cursor: default;
}

/* The selected tool. Bordered and tinted rather than filled, so a 24px line
   icon does not have to survive being inverted. */
.tool-btn.on[b-8ehzsgdyag] {
    border-color: var(--sp-signal);
    box-shadow: inset 0 0 0 1px var(--sp-signal);
    color: var(--sp-ink);
}

.tool-btn:focus-visible[b-8ehzsgdyag],
.swatch:focus-visible[b-8ehzsgdyag] {
    outline: 2px solid var(--sp-signal);
    outline-offset: 2px;
}

.ico[b-8ehzsgdyag] {
    width: 1.25rem;
    height: 1.25rem;
    /* The icon is stroked with currentColor, so it follows the button's own
       state - selected, disabled, hovered - with no rule of its own. */
    flex: 0 0 auto;
}

/* Six characters at most - "Triangle" is the longest and fits at this size.
   Anything longer wants a shorter word, not a smaller font. */
.tool-name[b-8ehzsgdyag] {
    font-size: 0.55rem;
    line-height: 1;
    letter-spacing: 0.01em;
    color: var(--sp-dim);
}

.tool-btn.on .tool-name[b-8ehzsgdyag] { color: var(--sp-ink); }

/* The width preview. --bar is set inline per button, in SCREEN pixels: see
   BarPx() in the component, and the note there about why it is not the document
   width it names. */
.w-bar[b-8ehzsgdyag] {
    width: 1.3rem;
    height: var(--bar);
    border-radius: 4px;
    background: currentColor;
}

/*  A colour swatch. The colour itself comes from --swatch, set inline per button,
    which is the one thing here that genuinely varies per element and cannot be a
    class. It fills its grid cell, so the six sit four-then-two like every other
    section. */
.swatch[b-8ehzsgdyag] {
    width: 100%;
    aspect-ratio: 1 / 1;
    padding: 0;
    border: 2px solid var(--sp-line);
    border-radius: 6px;
    background: var(--swatch);
    cursor: pointer;
}

.swatch:hover[b-8ehzsgdyag] { border-color: var(--sp-dim); }

/* The selected colour is ringed rather than tinted: a tint would change the
   swatch's own colour, which is the one thing it exists to show. */
.swatch.on[b-8ehzsgdyag] {
    border-color: #ffffff;
    box-shadow: 0 0 0 2px var(--sp-signal);
}

/* min-width: 0 is load-bearing on a flex child holding a percentage-width
   canvas: without it the item's automatic minimum size is its content, and the
   canvas pushes the toolbox off the row instead of taking what is left. */
.pad-wrap[b-8ehzsgdyag] {
    flex: 1 1 20rem;
    min-width: 0;
    display: flex;
    justify-content: center;
}

/*  THE CANVAS ELEMENT.

    ⚠ IT HAS NO height AND NO aspect-ratio HERE. scratchpad.js sets aspectRatio
    inline from the DOCUMENT's own w and h when it attaches, so a drawing stored
    at a different shape still displays correctly. Declaring a ratio in this file
    would be a second opinion about the same thing, and this one would be wrong
    for any drawing that is not 16:10.

    The max-width is what keeps a wide screen from producing a canvas taller than
    the window - it bounds the WIDTH by the height available, times the 1.6 of the
    default canvas. ⚠ THE 14rem IS THE CHROME ABOVE AND BELOW THIS ELEMENT, so it
    has to be revisited whenever a row is added to or taken off the page: it was
    17rem while the toolbar was a band above the canvas, and moving those controls
    into a column beside it gave three rem of drawing back. On a drawing with a different stored ratio that bound is
    approximate, so the canvas simply comes out a little smaller than it could;
    it can never overflow, because height still follows from the aspect ratio.

    touch-action is also set inline by the script, on purpose: without it the
    first drag on a phone scrolls the page instead of drawing, and having the rule
    travel with the listeners means a canvas can never be live without it. It is
    repeated here so the element is correct between first paint and attach. */
.pad[b-8ehzsgdyag] {
    width: 100%;
    max-width: min(100%, calc((100vh - 14rem) * 1.6));
    height: auto;
    background: #0f1720;
    border: 1px solid var(--sp-line);
    border-radius: 8px;
    touch-action: none;
    /* The cursor is the whole affordance that this is a drawing surface - there is
       no other chrome on it. */
    cursor: crosshair;
}
/* /Pages/NoteEditor.razor.rz.scp.css */
/*  The writing page. A header row and a box, and deliberately nothing else. */

/* Wide enough for a real title and no wider - it sits in a flex row with the save
   button and the status, and a full-width input would push both off a phone.
   Same value as the drawing editor's, so the two screens' header rows line up. */
.title-box[b-vuck55n8s7] {
    max-width: 22rem;
    flex: 1 1 12rem;
}

.status[b-vuck55n8s7] {
    font-size: 0.85rem;
    color: var(--sp-dim);
    white-space: nowrap;
}

.status.unsaved[b-vuck55n8s7] {
    color: var(--sp-signal);
    font-weight: 600;
}

/*  THE TEXT BOX.

    Monospace comes from the global .font-monospace class on the element rather
    than from here, which is what makes it the same treatment identifiers get
    everywhere else on the domain.

    ⚠ resize: vertical, NOT both. A textarea a person can drag WIDER can be
    dragged past the edge of its container, and the page then scrolls sideways -
    the one thing MainLayout's `min-width: 0` exists to prevent. Height is theirs
    to change; width belongs to the layout.

    The min-height is a floor under the rows attribute so the box does not collapse
    on a short window into something too small to write in, and the line-height is
    looser than the app default because this is prose being read back, not a table
    being scanned. */
.body-box[b-vuck55n8s7] {
    min-height: 24rem;
    resize: vertical;
    line-height: 1.55;
    /* Tabs land where a person expects rather than eight characters away, which
       matters for the pasted-in lists this box will mostly hold. */
    tab-size: 4;
}
/* /Pages/Notes.razor.rz.scp.css */
/*  The opening of a note, in the list.

    ⚠ THE WIDTH CAP IS WHAT KEEPS THIS TABLE READABLE. An excerpt is up to 160
    characters on one line; without a bound it takes as much width as it wants and
    squeezes the four columns that carry the actual facts down to nothing. The
    ellipsis is the browser's, so the cut lands wherever the column happens to end
    rather than at a character count - which is right, because the column width is
    what is actually running out.

    The same rule is in Dashboard.razor.css. Two copies of five lines, and that is
    the correct answer for a SCOPED sheet: Blazor stamps a different attribute per
    component, so a shared rule would have to live in studio.css - a global
    selector called `.excerpt` claiming that name for the whole domain, to serve
    two tables. */
.excerpt[b-f15zz45rr3] {
    max-width: 30rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
