/*
  GHour — the hour grid.

  Tokens, reset, forms, buttons and feedback come from GNexus.Ui
  (gnexus.css). What is here is this app's own shape, and it is a different
  shape from every GNexus surface so far: the Portal and Console are a
  low-density admin shell used at a desk, and this is twenty-four rows read
  on a phone at 11pm.

  That is why gnexus-admin.css is NOT linked, and why the sizing below is
  bigger than the shared defaults rather than smaller. There are no
  responsive breakpoints in the shared core at all -- every app before this
  one was used at a desk -- so this file carries its own until that is worth
  pushing down into the library.
*/

/* ---- app shell -------------------------------------------------------- */

/* Respects a phone's notch and home indicator. viewport-fit=cover in
   index.html is what makes the grid reach the edges; this pads it back. */
body {
    padding: env(safe-area-inset-top) env(safe-area-inset-right)
             env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.booting {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    color: var(--muted);
}

/* A phone-width reading column, and no wider — until there is a screen to
   spend. The grid is a TABLE: twenty-four rows of five columns, and cramming
   it into 34rem on a 1400px monitor squeezes the activity for no reason
   while two thirds of the window sits empty. Widened per breakpoint below. */
main {
    max-width: 34rem;
    margin: 0 auto;
    padding: 1rem 1rem 6rem;
}

/* Form controls do NOT follow the page out. A text box stretched across a
   wide monitor is harder to use, not easier — the eye loses the line, and
   nobody types a company name that needs 1200px. The containers fill; what
   you read and type stays a comfortable width. */
.card input,
.card select,
.card textarea {
    max-width: 34rem;
}

/* ---- the shell: menu + page ------------------------------------------- */

/* One menu, two shapes. On a phone it is a drawer behind a button; past
   the breakpoint it is a permanent column. The same markup either way —
   a second copy for wide screens is how the two quietly stop matching. */
:root {
    /* The bar height, as a number the LAYOUT can read. The drawer and the
       scrim start below it, so the bar is never covered — and that only
       works if all three agree on one value. */
    --bar-h: 2.75rem;
}

/* A teal bar, not a white one. It is the only element on every screen, so
   it is where the product says what it is — and it separates the app chrome
   from the day, which is the part somebody is actually reading. */
.shell-bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: var(--bar-h);
    position: relative;

    /* Tight. The bar is chrome on every screen of an app whose page is a
       24-row table, so every pixel it takes is a row somebody cannot see.
       6px here rather than 12px — the height is then set by the tallest
       control rather than by air around it. */
    padding: 0.375rem 1rem;
    color: var(--bar-text);
    background: var(--bar);
}


/* Truly centred on the bar, so it does not drift as the business name
   changes length. Absolute, therefore, rather than a flex child between two
   groups of different widths.

   pointer-events:none because it now sits OVER the bar: it is a label, and
   an invisible box across the middle of the header would swallow taps meant
   for whatever is behind it. */
.bar-brand {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    pointer-events: none;
}
/* Inverted on the bar: the mark is teal-on-white everywhere else, and
   teal-on-teal is nothing at all. */
.shell-bar .mark {
    width: 1.5rem;
    height: 1.5rem;
    padding: 0.2rem;

    /* Inverted on the bar: the mark is teal-on-white everywhere else, and
       teal-on-teal is nothing at all. The SVG inside draws with
       currentColor, so this one line colours the glyph too. */
    color: var(--bar);
    background: var(--bar-text);
}

.shell-bar .mark svg { width: 100%; height: 100%; }

.shell-bar h1 { font-size: 1.05rem; line-height: 1.2; margin: 0; }

/* Who, and whose log — pushed to the far end of the header.
 *
 * min-width:0 is what makes it the thing that shrinks: without it a long
 * business name refuses to narrow and pushes the menu button off a phone.
 * The business ellipsises; the person's name does not, because it is the
 * shorter of the two and the one somebody checks at a glance. */
.shell-bar .whoami {
    margin: 0 0 0 auto;
    min-width: 0;

    /* Can never reach the centred brand. It ellipsises already, so the cap
       costs a few characters of a long business name rather than letting
       two pieces of text overlap in the middle of the header. */
    max-width: calc(50% - 5rem);
    text-align: right;
    font-size: 0.75rem;
    line-height: 1.2;

    /* Mixed from the bar text rather than --muted: --muted is tuned against
       the PAGE, and on teal it is neither readable nor quiet. */
    color: color-mix(in srgb, var(--bar-text) 78%, transparent);
}

.shell-bar .whoami strong {
    display: block;
    color: var(--bar-text);
    font-weight: 500;
}

.shell-bar .whoami .at {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.menu-toggle {
    margin: 0;
    padding: 0.4rem 0.6rem;
    font-size: 1.1rem;
    line-height: 1;

    /* Sits ON the bar, so it takes its colours from the bar. --border is a
       page token and vanishes against teal. */
    color: var(--bar-text);
    background: none;
    border: 1px solid color-mix(in srgb, var(--bar-text) 40%, transparent);
    border-radius: 8px;
}

.menu-toggle:hover:not(:disabled) {
    background: color-mix(in srgb, var(--bar-text) 14%, transparent);
    filter: none;
}

/* Off-canvas rather than hidden: it slides, and a screen reader can still
   reach it in source order. Width is capped so it never becomes a second
   page on a large phone. */
/* BELOW the bar, not over it. The drawer used to start at the top of the
   window and cover the header — including the hamburger that opened it, and
   the name of the business whose log is open. The bar is the one thing on
   screen at all times; a menu that hides it takes away the answer to "where
   am I" at the exact moment somebody is deciding where to go. */
.shell-menu {
    position: fixed;
    top: var(--bar-h);
    bottom: 0;
    left: 0;
    z-index: 20;
    width: min(17rem, 80vw);
    padding: 1rem;
    overflow-y: auto;

    /* Plain white. The teal band at its head is what separates the panel
       from the page now, so a tint underneath the whole thing only muddies
       the one colour that is doing the work. */
    background: var(--surface);
    border-right: 1px solid var(--border);
    transform: translateX(-100%);
    transition: transform 0.18s ease-out;
}

.shell.menu-open .shell-menu { transform: translateX(0); }

/* Sits UNDER the drawer and over the page, and is a real button so that
   tapping away closes it for a keyboard too. */
.scrim {
    position: fixed;

    /* Stops at the bar too, so the header stays both visible AND usable —
       a scrim over it would swallow the tap that closes the drawer. */
    inset: var(--bar-h) 0 0 0;
    z-index: 10;
    margin: 0;
    padding: 0;
    background: rgba(0, 0, 0, 0.35);
    border: none;
    border-radius: 0;
}

/* A button that reads as a link. Sign out is a button because it acts
   rather than navigates, and it should not carry a primary button's
   weight beside three plain destinations. */
.linkish {
    margin: 0;
    padding: 0;
    font: inherit;
    color: var(--text);
    background: none;
    border: none;
    cursor: pointer;
}

.linkish:hover:not(:disabled) { filter: none; }

/* The band at the head of the drawer. Bled to the panel edges with negative
   margins against .shell-menu's 1rem padding, so it reads as a header for
   the panel rather than as a box sitting inside it.

   It wears the BAR's colours, not the accent's: the drawer slides out from
   under the bar, and the two being the same teal is what makes it read as one
   piece of chrome instead of two teal things that nearly match. */
.menu-brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: -1rem -1rem 0.75rem;
    padding: 0.7rem 1rem;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--bar-text);
    background: var(--bar);
}

/* Bigger than the bar's, because here it is the only thing carrying the
   product's name rather than sitting beside a heading. */
.menu-brand .mark {
    width: 1.85rem;
    height: 1.85rem;
    padding: 0.24rem;
    color: var(--bar);
    background: var(--bar-text);
}

.menu-brand .mark svg { width: 100%; height: 100%; }

.menu ul { list-style: none; margin: 0; padding: 0; }

.menu a,
.menu .linkish {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    padding: 0.6rem 0.7rem;
    text-align: left;
    font-size: 0.95rem;
    color: var(--text);
    text-decoration: none;
    border-radius: 8px;
}

/* Washes of the accent, not --bg. The panel is tinted now, and --bg is
   within a hair of it — hover and the current page were both landing on a
   colour indistinguishable from the thing behind them. */
.menu a:hover, .menu .linkish:hover {
    background: color-mix(in srgb, var(--accent) 9%, var(--surface));
}

/* NavLink marks the current page itself. Marked rather than removed: the
   entry you are on still tells you where you are. */
/* The current page: a teal wash AND a teal edge down its left. The wash
   alone is faint on white; the edge is what makes it findable at a glance.
   An inset shadow rather than a border, so it follows the rounded corner
   and does not shift the label by 3px. */
.menu a.active {
    font-weight: 600;
    color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, var(--surface));
    box-shadow: inset 3px 0 0 var(--accent);
}

/* The icons. Muted at rest so the WORDS are what the eye reads down — an
   icon per row in full colour turns a six-item list into a six-colour
   pattern, and nobody navigates by pattern. They mark the current entry
   with it instead. */
.menu .ico {
    width: 1.15rem;
    height: 1.15rem;
    flex: 0 0 auto;
    color: var(--muted);
}

.menu a:hover .ico,
.menu .linkish:hover .ico { color: var(--text); }

.menu a.active .ico { color: var(--accent); }

.menu-account {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.menu-who { padding: 0 0.7rem; font-weight: 500; }

/* WHOSE LOG this is, and never behind another tap. One person may keep
   their own log and their employer's (ADR 0017); a grid of hours with no
   name on it does not say which is open. */
.menu-at {
    padding: 0 0.7rem 0.6rem;
    font-size: 0.85rem;
    color: var(--muted);
}

/* ---- the date strip --------------------------------------------------- */

.datebar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.datebar .today { flex: 1; text-align: center; font-weight: 500; }

/* Thumb-sized, not mouse-sized. The shared button sizing is tuned for a
   pointer; these are tapped one-handed while standing up. */
.datebar button {
    margin: 0;
    min-width: 3rem;
    min-height: 2.75rem;
    padding: 0.5rem 0.75rem;
}


/* ---- the day's toolbar ------------------------------------------------ */

/* Everything above the grid, on ONE line: the date, the week link, the
   template picker, and saving the day as a template. These were four
   stacked blocks and took half the screen before an hour was visible.

   It WRAPS rather than scrolls. On a phone it settles into two or three
   lines, which is still a fraction of what it replaced, and every control
   stays reachable — a horizontally scrolling toolbar hides its right-hand
   end, and the thing hidden here would be "save this day as a template". */
.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

/* The date carries the eye, so it stays a size up from the controls beside
   it. NOT flex:1 like .datebar's: pushed to the middle of a wide row it
   would sit nowhere in particular, and the arrows would drift apart. */
.toolbar .today {
    font-weight: 600;
    font-size: 1.05rem;
    white-space: nowrap;
}

/* Thumb-sized, not mouse-sized — tapped one-handed while standing up. */
.toolbar button {
    margin: 0;
    min-height: 2.75rem;
    padding: 0.5rem 0.9rem;
}

.toolbar button[aria-label^="Previous"],
.toolbar button[aria-label^="Next"] { min-width: 3rem; }

.toolbar select {
    margin: 0;
    width: auto;
    min-height: 2.75rem;
    max-width: 12rem;
}

.toolbar label,
.toolbar .hint { margin: 0; white-space: nowrap; }

/* The picker and its Apply travel together: a combo that wraps away from
   the button acting on it stops reading as one control. */
.toolgroup {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

/* Pushes the week link away from the arrows, so "‹ Tue 22 Sep ›" stays a
   group and does not read as four peers. */
.toolbar > .hint:first-of-type { margin: 0 0.5rem; }

/* ---- the grid --------------------------------------------------------- */

/* Rules BETWEEN rows are gone with the stripes arriving. A line and a
   stripe do the same job, and doing it twice on twenty-four rows is noise —
   the table only needs edges, so the list keeps its own. */
.grid {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid var(--border);
}

/* DIRECT children only. Without the >, this reached the <li>s inside the
   combo list — which lives in the editor, which is itself an <li> in this
   grid — and made each option a four-column hour row. The option button
   landed in the 3.25rem START column, 64px wide, so "Deep work" folded
   onto two lines inside a 544px list. Measured, not guessed. */
/* The row carries the colour, and every cell in it is transparent, so a
   stripe runs the FULL width. The activity button used to paint itself
   --surface while the cells beside it showed the page through, which made
   the strongest line on the table a vertical one — a column band where the
   thing being read is a row. */
.grid > li {
    display: grid;
    background: var(--surface);
    /* The company track is 5.25rem, not 4.5. "Personal" — the default, so
       the value most rows carry — needed 60.6px of pill inside 60.8px of
       cell and lost to sub-pixel rounding, rendering "Perso…" on nearly
       every row of a phone. Measured, not guessed; the extra 12px comes
       off the activity column, which has 167px and can spare it. */
    grid-template-columns: 3.25rem 1fr 5.25rem 3.25rem;
    align-items: stretch;
}

/* Every other hour, in a wash of the platform teal rather than a neutral
   grey — the stripe then belongs to the product instead of looking like the
   page showing through.

   Mixed FROM --accent, so it follows the theme: change the accent once and
   the stripes follow, and dark mode needs no second definition. 7% is the
   ceiling before it stops reading as a stripe and starts reading as a
   STATE — these rows are not selected, flagged or different in any way, and
   an hour grid is exactly where a false "this one matters" costs someone a
   second look. */
.grid > li.alt { background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }

.grid > li:last-child { border-bottom: 1px solid var(--border); }

/* The editor, opened between two hours.
 *
 * Its own row rather than a card after the grid: it cannot then be off the
 * fold, whatever the screen, and the hour it belongs to is the line above
 * it rather than something to remember. `display: block` because it is not
 * a grid row of columns — it holds one card — and the card fills it. */
.grid > li.editrow {
    display: block;
    padding: 0;
    background: var(--bg);
}

.grid > li.editrow .card {
    margin: 0;
    border-radius: 0;
    border-left: none;
    border-right: none;
}

/* Already the fifth or sixth control the eye meets; it does not also need
   the page's biggest heading. */
.grid > li.editrow .card h2 { font-size: 1rem; }

.hour {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0.5rem 0.6rem 0.5rem 0;
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

/* The hour a row ENDS at. "09:00" on its own does not say whether the row
   is the hour starting there or the one ending there, and an hour grid is
   exactly where that ambiguity costs somebody an hour. The two times
   bracket the row — start hugging the activity from the left, end from the
   right — so each row reads as the span it is. */
.hour-end {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    padding: 0.5rem 0 0.5rem 0.6rem;
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

/* Named once at the top rather than repeated down twenty-four rows. */
.gridhead {
    display: grid;
    grid-template-columns: 3.25rem 1fr 5.25rem 3.25rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    margin-top: 1rem;
}

.gridhead .s { text-align: right; padding-right: 0.6rem; }
.gridhead .e { text-align: left; padding-left: 0.6rem; }

/* The activity's category. Sits OUTSIDE the slot button on purpose: it is
   a property of the activity rather than of this hour, and putting it
   inside the row's tap target would suggest tapping edits it here. */
.cat {
    display: flex;
    align-items: center;
    padding: 0.5rem 0.35rem;
    overflow: hidden;
}

/* gnexus.css gives .pill its SHAPE only — the colours live in the
   variants in gnexus-admin.css, which this app does not link (ADR 0010).
   So the skin is local, or the pill renders as bare text.

   Allowed to shrink and ellipsis rather than push the activity column
   narrower: the activity is what people read down the grid, and a long
   category name must not cost it width. */
.cat .pill {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--muted);
    background: var(--bg);
    border: 1px solid var(--border);
}

/* The whole row is the target. A 13px checkbox is a poor thing to aim at,
   and this is the control somebody uses twenty-four times a day. */
.slot {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    min-height: 3rem;
    width: 100%;
    margin: 0;
    padding: 0.5rem 0.7rem;
    text-align: left;
    font: inherit;
    color: var(--text);
    background: transparent;
    border: none;
    border-left: 3px solid transparent;
    border-radius: 0;
    cursor: pointer;
}

/* A wash of the accent rather than the page grey: the slot is transparent
   now, so a solid hover colour would sit on the stripe as a block. */
.slot:hover:not(:disabled) {
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    filter: none;
}

.slot .what { font-size: 0.95rem; }
.slot .plan { font-size: 0.8rem; color: var(--muted); }

/* The reminder attached to this particular hour. Muted and italic so it
   reads as an aside to the activity rather than a second activity. */
.slot .note { font-size: 0.8rem; color: var(--muted); font-style: italic; }

/* An unlogged hour is not styled as an error. Gaps are the honest state of
   a day somebody has not finished writing down, and this is the whole
   reason nothing is stored for an hour nobody logged. */
.slot.empty .what { color: var(--muted); font-style: italic; }

/* The plan showing through where nothing was recorded. This IS the product
   -- the workbook could not hold the disagreement at all. */
.slot.planned-only { border-left-color: var(--border); }
.slot.matches { border-left-color: var(--ok); }
.slot.differs { border-left-color: var(--danger); }

/* ---- the editor ------------------------------------------------------- */

.editor { margin-top: 1rem; }
.editor .actions { margin-top: 1rem; }

/* Which record this hour is being written into. A real checkbox, sized up
   from the browser default so it is a thumb target rather than a mouse one,
   and the whole label is the hit area. */
.editor .check {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: 0 0 1rem;
    padding: 0.55rem 0.7rem;
    font-weight: 500;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    cursor: pointer;
}

.editor .check input[type="checkbox"] {
    width: 1.1rem;
    height: 1.1rem;
    margin: 0;
    flex: 0 0 auto;
    accent-color: var(--accent);
}

/* Says plainly that this is the plan, once it is. */
.editor .check:has(input:checked) {
    border-color: var(--accent);
    background: var(--surface);
}

.suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.6rem;
}

/* Recent activities, one tap each. The order is not cosmetic: it is what
   keeps logging an hour to a tap rather than a search. */
.suggestions button {
    margin: 0;
    padding: 0.45rem 0.7rem;
    font-size: 0.85rem;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
}

.suggestions button:hover:not(:disabled) { border-color: var(--accent); filter: none; }

/* Category headings on the activities list. Quiet, because they are
   signposts through somebody's own vocabulary rather than titles. */
.catgroup {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 1.5rem 0 0;
    font-size: 0.95rem;
}

/* The count and the Rename control share this span, and without a gap they
   render as "1 activityRename". */
.catgroup .hint { display: inline-flex; align-items: baseline; gap: 0.6rem; }

/* ---- the week --------------------------------------------------------- */

.bars { list-style: none; margin: 1rem 0 0; padding: 0; }
.bars li + li { margin-top: 0.75rem; }

.bar-label {
    display: flex;
    justify-content: space-between;
    font-size: 0.85rem;
    margin-bottom: 0.25rem;
}

.bar-label .n { color: var(--muted); font-variant-numeric: tabular-nums; }

.bar { height: 0.5rem; background: var(--bg); border-radius: 999px; overflow: hidden; }
.bar span { display: block; height: 100%; background: var(--accent); }

/* Planned but not lived, drawn under the actual so the shortfall is the
   visible part rather than a number to work out. */
.bar.plan span { background: var(--border); }

/* The billing details under a company's bar. Quiet, because it is
   reference rather than a number to read. */
.billto {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    margin-top: 0.35rem;
    font-size: 0.8rem;
    color: var(--muted);
}

.records .n { font-variant-numeric: tabular-nums; white-space: nowrap; }

.observations { margin: 1.25rem 0 0; padding-left: 1.1rem; }
.observations li { margin-bottom: 0.4rem; font-size: 0.925rem; }


/* ---- templates -------------------------------------------------------- */

/* A plain record list. Deliberately NOT gnexus-admin.css's .records: that
   sheet is a desk-width admin shell (ADR 0010), and this is the same
   phone-first column as the hour grid above it. */
.records { list-style: none; margin: 1rem 0 0; padding: 0; }

.records li {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    justify-content: space-between;
    padding: 0.85rem 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}

.records li + li { margin-top: 0.6rem; }
.records .sub { display: block; font-size: 0.85rem; margin: 0.15rem 0 0; }

/* Its own class rather than .actions, whose buttons carry a 1.25rem top
   margin meant for the bottom of a form — inside a row that just pushes
   the buttons out of line with the name they belong to. */
.row-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.row-actions button {
    margin-top: 0;
    padding: 0.45rem 0.8rem;
    font-size: 0.85rem;
}
/* CATEGORY is hidden on a phone, COMPANY is not.
 *
 * Five columns at 375px squeezed the activity to 103px, and that is the
 * column people read down — the one thing the grid is for. Something had
 * to give, and between the two it is the category: it says what KIND of
 * hour this was, which is a question the week report answers, while the
 * company is who it was FOR, which is the one a timesheet needs and the
 * one that cannot be derived from anything else on the row.
 *
 * The header cell goes with it, or the columns stop lining up. */
@media (max-width: 39.99rem) {
    .grid > li .cat.category,
    .gridhead .cat-head { display: none; }

    /* And the activity gets its gutters back. Widening the company track
       enough for "Personal" left "GHour Project Work" 1.6px short of one
       line, so the commonest activity wrapped on every row it appeared on.
       0.2rem of padding either side buys 6.4px — more than the shortfall,
       and taken from whitespace rather than from another column. */
    .grid > li .slot { padding-left: 0.5rem; padding-right: 0.5rem; }
}


/* ---- narrow phones ---------------------------------------------------- */

/* Below 30rem the centred word "GHour" is the least useful thing on the bar
   and it was costing the most: the identity has to stop short of it, so the
   BUSINESS name — the thing ADR 0017 says is owed to somebody keeping
   several logs — was truncating to "at Easwar GHo…". The mark alone says
   which app this is; the name says which log. */
@media (max-width: 29.99rem) {
    /* Hidden from the EYE, not from the accessibility tree. display:none
       would take the page's only <h1> with it, and a screen reader
       navigating by heading would find nothing at the top of the app. */
    .bar-brand h1 {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* And the identity gets the room back — it now only has to clear a
       24px mark rather than a word beside it. */
    .shell-bar .whoami { max-width: calc(50% - 2.25rem); }
}
/* ---- wider screens ---------------------------------------------------- */

@media (min-width: 40rem) {
    main { max-width: 52rem; padding: 2rem 1.5rem 4rem; }
    .grid > li { grid-template-columns: 4rem 1fr 6rem 6rem 4rem; }
    .gridhead { grid-template-columns: 4rem 1fr 6rem 6rem 4rem; }
}

/* At desk width the menu stops being a drawer and simply stays open. The
   same markup, so the two shapes cannot drift apart — only the position
   changes, and the button that revealed it disappears rather than becoming
   a control that does nothing. */
@media (min-width: 60rem) {
    .shell-menu {
        position: fixed;
        transform: none;
        width: 15rem;
        border-right: 1px solid var(--border);
    }

    .menu-toggle { display: none; }

    /* And the menu's own band goes with it. It exists to give the DRAWER a
       head; at desk width there is no drawer, just a column sitting directly
       under a bar that already says the name — so the band put "GHour" twice,
       one line apart. */
    .menu-brand { display: none; }

    /* Fills what the menu leaves. The grid is the reason: five columns of
       twenty-four rows read better with room, and the window has it. */
    main { max-width: none; margin-left: 15rem; margin-right: 0; }

    /* Never drawn: with the menu permanently open there is nothing to
       dismiss, and a scrim would sit over the page. */
    .scrim { display: none; }
}

/* ---- error banner ----------------------------------------------------- */

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 0.7rem 1rem;
    color: var(--danger);
    background: var(--danger-bg);
    box-shadow: 0 -1px 0 var(--border);
}

#blazor-error-ui .dismiss { float: right; cursor: pointer; }

/* ---- the window IS the page ------------------------------------------- */

/* Nothing scrolls the document. The shell is exactly as tall as the window,
   the bar stays where it was put, and the LIST scrolls inside it.

   The payoff is the column headings: START / COMPANY / CATEGORY / END are
   headed ONCE rather than repeated on every row, which only works while
   they are on screen. Scrolling the document took them away at hour four
   and left twenty rows of unlabelled times — precisely the ambiguity that
   header exists to settle. The toolbar stays too, so moving to another day
   never means scrolling back up first.

   100dvh, not 100vh: on a phone the browser's own chrome grows and shrinks
   as you scroll, and 100vh is the LARGEST of those, which puts the bottom
   of the app under the address bar. */
/* #app is in this chain, not decoration: Blazor mounts the layout INSIDE
   it, so height:100% on .shell resolves against #app. Leave it out and the
   percentage has no definite parent to measure, falls back to auto, and the
   shell grows to its content — which is the document scrolling again, with
   every rule below looking correct. */
html, body, #app { height: 100%; }
body { overflow: hidden; }

.shell {
    height: 100%;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.shell-bar { flex: 0 0 auto; }

/* min-height:0 is load-bearing on both this and the grid below. A flex item
   defaults to min-height:auto, which refuses to shrink below its content —
   so twenty-four rows would push the shell past the window and the document
   would scroll after all, which is the whole thing being fixed here. */
main {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* The DAY is the page whose content is one long list, so it hands its
   scroll to the list rather than taking it. Everything else — companies,
   activities, a week, a month, and the template editor with its Save
   underneath the grid — scrolls in main as normal.

   Keyed on .gridwrap rather than .grid: the template editor has a grid too,
   and buttons AFTER it that must not be pushed off the bottom.

   :has() rather than a class on <main>: a page cannot reach the layout's
   element from inside itself, and giving MainLayout a parameter for this
   would make every page declare a shape the CSS can already see. */
main:has(.gridwrap) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding-bottom: 0;
}

.gridwrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Inside the scroller with the rows, so a scrollbar narrows both by the
   same amount and the columns stay under the words that name them. */
.gridwrap .gridhead {
    position: sticky;
    top: 0;
    z-index: 2;
    margin-top: 0;
    padding: 0.5rem 0 0.45rem;

    /* Twice the stripe, so it reads as the HEADING of the table rather than
       as one more row of it — and it stays on screen while the hours move
       under it, which is the whole point of heading them once.

       Opaque, necessarily: a sticky element mixed with `transparent` would
       let twenty-four rows scroll through the words naming their columns. */
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 14%, var(--surface));
    border-bottom: 1px solid color-mix(in srgb, var(--accent) 25%, var(--surface));
}

/* ---- the combo box ---------------------------------------------------- */

/* Pick from the list, or type something that is not on it. Ordinary markup
   rather than <datalist>, so it can be styled at all — see Shared/Combo.razor
   for why the native one was not enough. */
.combo {
    position: relative;
    max-width: 34rem;
    margin-bottom: 1rem;
}

/* Room for the caret, so a long activity never runs underneath it. */
.combo .combo-input {
    width: 100%;
    margin: 0;
    padding-right: 2.5rem;
}

.combo-toggle {
    position: absolute;
    top: 0;
    right: 0;
    width: 2.5rem;
    height: 100%;
    margin: 0;
    padding: 0;
    font-size: 0.8rem;
    color: var(--muted);
    background: none;
    border: none;
    border-radius: 0 8px 8px 0;
    cursor: pointer;
}

.combo-toggle:hover:not(:disabled) { color: var(--fg); background: none; filter: none; }

.combo.open .combo-toggle span { display: inline-block; transform: rotate(180deg); }

/* IN FLOW, not floating over the fields below.
   An absolutely-positioned dropdown was the obvious choice and it was the
   wrong one HERE: the hour list scrolls inside .gridwrap (overflow-y:auto),
   and an overflow container CLIPS its absolutely-positioned descendants.
   So the list opened past the bottom of the scroller, showed one row, and
   could not be scrolled to — because an absolute box adds nothing to its
   container's scroll height. It read as "selection is not working";
   clicking the one visible option always worked fine.

   In flow it grows the editor, so the scroller can reach it and every
   option is clickable. The cost is that the fields below move down while
   it is open, which is the lesser problem by a distance. */
.combo-list {
    max-height: 12rem;
    margin: 0.35rem 0 0;
    padding: 0.25rem;
    overflow-y: auto;
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: none;
}

.combo-list button {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0.55rem 0.6rem;
    font-size: 0.95rem;
    font-weight: 400;
    text-align: left;
    color: var(--fg);
    background: none;
    border: none;
    border-radius: 6px;
    cursor: pointer;
}

.combo-list button:hover,
.combo-list button:focus-visible {
    background: var(--bg);
    filter: none;
}

/* The one already chosen, so opening the list says where you are in it. */
.combo-list button[aria-selected="true"] {
    font-weight: 600;
    color: var(--accent);
}

/* ---- the licence strip ------------------------------------------------ */

/* A full-width strip UNDER the bar, not a badge on it. On the bar it ran
   straight over the centred brand — which is absolutely positioned, so
   nothing else may sit at that end of the row.

   Amber, not red: nothing is broken and nothing is lost. A bill has not been
   paid, and shouting about it at somebody who probably cannot pay it
   themselves is the wrong volume. */
.licence-strip {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    flex: 0 0 auto;
    padding: 0.5rem 1rem;
    font-size: 0.85rem;
    line-height: 1.35;
    text-decoration: none;
    color: var(--warn-text);
    background: var(--warn-bg);
    border-bottom: 1px solid color-mix(in srgb, var(--warn-text) 25%, transparent);
}

.licence-strip .mark-warn {
    flex: 0 0 auto;
    width: 1.15rem;
    height: 1.15rem;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 0.8rem;
    border-radius: 50%;
    color: var(--warn-bg);
    background: var(--warn-text);
}

/* The same warning as a card on the licence page itself. */
.card.warn {
    border-color: color-mix(in srgb, var(--warn-text) 35%, var(--border));
    background: var(--warn-bg);
}

.card.warn h2 { margin-top: 0; color: var(--warn-text); }
