/* =============================================================================
 * app.css - the shared component layer for every Nayive app.
 *
 * WHAT THIS IS
 *   Nayive is fourteen small single-page apps that must feel like one product. The
 *   colour tokens and a few widgets already lived in shared/theme.css; this
 *   file adds the rest of the common UI - the app shell, the header, the icon
 *   button, the dialog, the form field, pills and cards - so an app's own
 *   <style> block only has to hold what is genuinely unique to that app.
 *
 *   THIS FILE (plus theme.css and the ui.js header comment) IS the Nayive style
 *   guide - the only one. Each block below is preceded by a comment that says
 *   what the component is, when to use it, and the rules it must follow. If you
 *   are building or changing Nayive UI, read the comment for the component you
 *   are touching. There is no separate style-guide document to keep in sync.
 *
 * HOW TO LOAD  (in every app's <head>, in this order)
 *     <script src="../shared/i18n.js"></script>             <!-- FIRST: hides the page until the dictionary is in -->
 *     <meta name="theme-color" content="#1E1F23">           <!-- theme.js rewrites it to the live --bg -->
 *     <link rel="stylesheet" href="../shared/theme.css">    <!-- tokens first -->
 *     <link rel="stylesheet" href="../shared/app.css">
 *     <script>try{ if(localStorage.getItem('balata-theme')!=='dark')
 *         document.documentElement.setAttribute('data-theme','light'); }catch(e){}</script>
 *     <script src="../shared/theme.js" defer></script>      <!-- scheme toggle + sync -->
 *     <script src="../shared/gum-api.js" defer></script>    <!-- GumApi: the file API -->
 *     <script src="../shared/ui.js" defer></script>         <!-- NayiveUI: sheets, toast, buttons -->
 *     ... then the app's own <style> ...
 *   Light is the default scheme; only an explicit "dark" choice is dark. The
 *   inline script applies the saved scheme before first paint (no flash).
 *   The app's <style> comes last, so on an equal-specificity clash the app
 *   wins - use that only for real per-app layout, never to restyle a shared
 *   component into a different look. PWA manifests set theme_color /
 *   background_color to #1E1F23.
 *
 * THE TOKENS  (defined in theme.css - use the name, never the literal)
 *   colour     --bg --card --card2 --line --text --text-dim --text-faint
 *              --accent --on-accent --on-solid --danger --link --ok --warn --busy --shadow
 *              (each has a one-line "what it's for" comment in theme.css)
 *   type       --font-sans --font-mono
 *   radius     --radius-s (8) --radius-m (12) --radius-l (16) --radius-pill (999)
 *   spacing    --space-1..5  (4 8 12 16 24)
 *   elevation  --elev-card --elev-dialog --scrim
 *   motion     --tr-fast (colour) --tr-move (transform)
 *
 * TYPE SIZES  (rem; weight 700 where noted)
 *   app title (h1 / .app-title) 1.1  --accent 700    dialog title (h2)  1.15  700
 *   body / list rows            0.9 - 0.95           field label / meta 0.8  --text-dim
 *   section label               0.72 700 uppercase, letter-spacing 0.06em, --text-dim
 *
 * GLOBAL RULES
 *   - Never write a hex colour, a raw box-shadow, or an off-scale corner radius
 *     in an app. Add the value to theme.css if the scale is missing something.
 *   - Hover / pressed tints use color-mix(in srgb, var(--text) N%, transparent)
 *     so they flip light-on-dark and dark-on-light. Never a fixed rgba().
 *   - An accent fill means "active / selected / primary". It is not a hover
 *     colour - hover is a neutral --text tint.
 *   - Every icon-only control needs a `title` and an `aria-label` - as i18n
 *     keys (data-i18n-attr), never literal text.
 *   - Every date shown to a person is yyyy-mm-dd. No locale or month-name form.
 *   - No user-facing text lives in the source: every message is a key in
 *     shared/i18n/*.json (see docs/i18n.md). App names keep their short label
 *     ("Calc", "Drive", ...) in every language.
 *   - A dialog closes on its own × / ✓ buttons and the Esc key only - never on
 *     a click on the backdrop. shared/ui.js wires both for every static
 *     .sheet-backdrop (a click on its data-act="close" button, Escape on the
 *     top-most open one), so an app only adds a handler when closing must
 *     also reset state.
 *   - An element with the `hidden` attribute is hidden, whatever display its
 *     class sets ([hidden] below is !important). Use it instead of a local
 *     `.x[hidden] { display: none }` line.
 *
 * TWO LAYOUT MODES
 *   Every app has two layouts and ONE breakpoint between them:
 *     - PC + tablet  (wider than 640px): the default rules in every file. An
 *       iPad in portrait (768px+) gets this layout on purpose - it has the room.
 *     - Phone        (640px and under): one `@media (max-width: 640px)` block
 *       per app plus the shared PHONE block at the end of this file.
 *   640px is the phone break in EVERY app; do not invent a second one.
 *   The one thing an app may add above it is a PANE break - the width at which
 *   its side pane stops fitting and is dropped, which is not a phone at all:
 *   Music 760, Movies 820, Trips 920 (its route map), and the card shell's own
 *   480 for going edge to edge. An app with no side pane has only the 640.
 *   What the phone layout does, so every app does it the same way:
 *     - the header keeps the app name; secondary buttons go behind a "⋮" menu
 *       (calendar, contacts, drive) or the toolbar wraps onto more rows (text);
 *       side panes become a slide-in sheet (drive) or three icon buttons in a
 *       slim header row (planner);
 *     - a CROWDED TOOLBAR (write, calc) shrinks to ONE row of the controls that
 *       get used most - picked by name in that app's phone block, never by
 *       where they sit in the bar - with a "⋮" at the end that opens the whole
 *       toolbar in place and closes it again. Both also FOLD the row away while
 *       you type (write: you tap the text; calc: a cell editor opens) and an
 *       "Aa" button in the header brings it back. Their file buttons move to a
 *       second "⋮" in the header, and the "?" is moved there with them so help
 *       survives the fold. See the phone block in write/ or calc/index.html;
 *     - a dialog taller than the screen scrolls inside, with its button row
 *       stuck to the bottom edge (.sheet-actions - see the PHONE block);
 *     - touch targets are at least 36px and every text field is at least
 *       16px, so iOS never zooms the page into a field;
 *     - nothing is ever wider than the screen (no sideways page scroll).
 *
 * PER-APP OVERRIDES
 *   An app's <style> may carry a few small overrides of the rules here - a
 *   page that hides its own scroll, calc's tinted toolbar, trip's two-column
 *   layout, the compose-bar submit in tasks, the entry-screen styling in
 *   login.html / admin.html, the 30-32px "toolbar button" that calc / text /
 *   write use instead of .icon-btn. Each such override carries a one-line
 *   comment saying why. That comment is the record; there is no central list.
 * ===========================================================================*/


/* -----------------------------------------------------------------------------
 * BASE / RESET
 *
 * border-box everywhere. The page never scrolls sideways. The body carries the
 * UI font, the primary text colour and the page background so an app's <body>
 * rule can be deleted. Links are the accent-independent --link blue, underline
 * on hover only.
 * ---------------------------------------------------------------------------*/
*,
*::before,
*::after            { box-sizing: border-box; }

html               { overflow-x: hidden; overflow-y: auto; }

body
{
    margin: 0;
    font-family: var(--font-sans);
    color: var(--text);
    background: var(--bg);
    -webkit-tap-highlight-color: transparent;
}

a                  { color: var(--link); text-decoration: none; }
a:hover            { text-decoration: underline; opacity: 0.85; }

/* The hidden attribute always wins - a class that sets display: flex on the
   same element (an .icon-btn, a bar, a pane) must not un-hide it. */
[hidden]           { display: none !important; }


/* -----------------------------------------------------------------------------
 * APP SHELL - two layouts, pick one per app
 *
 * A) TOOL SHELL  (.app)  - calc, calendar, drive, text, write, planner
 *    A full-height flex column: a fixed .topbar on top, the work area (a grid,
 *    an editor, a map) fills the rest and does its own scrolling. The page
 *    background is --bg.
 *
 * B) CARD SHELL  (.page > .page-inner > .card)  - tasks, contact
 *    A single centred column that reads like a sheet of paper. Max width
 *    460px. On a real phone (<=480px) the card loses its border, radius and
 *    shadow and goes edge to edge. The edge-to-edge rule is scoped to
 *    .page > .page-inner > .card, so a standalone .card (login, a dialog-free
 *    panel) keeps its box at every width.
 *
 *    trip is a card app too but runs its own two-column variant (.trip-panel,
 *    not .card): the list on the left, a sticky route map on the right on a
 *    wide viewport.
 *
 * Do not mix A and B. An app is either a tool or a list/document.
 * ---------------------------------------------------------------------------*/
.app
{
    display: flex;
    flex-direction: column;
    height: 100%;
}

.page
{
    display: flex;
    justify-content: center;
    width: 100%;
    min-height: 100vh;
    padding: 28px 14px;
}

.page-inner        { width: 100%; max-width: 460px; }

.card
{
    padding: 20px 16px 16px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--elev-card);
}

@media (max-width: 480px)
{
    .page                        { padding: 0; }
    .page > .page-inner          { max-width: 100%; }
    .page > .page-inner > .card
    {
        min-height: 100vh;
        border: none;
        border-radius: 0;
        box-shadow: none;
    }
}


/* -----------------------------------------------------------------------------
 * HEADER
 *
 * Same shape in every app, same order left to right (his call, 2026-09-30:
 * "look on the left, act on the right"):
 *
 *   [<- back] [.app-icon] <h1> [file name] [.tb-modes] [pager]  ...
 *        ... .topbar-actions [ selection | search, + new, tools | more, settings, ? ]  [.sync-indicator]
 *
 * - LEFT, after the title: what you LOOK at - the view switch (.tb-modes:
 *   grid / list, day / week / month...), then a pager (Calendar's dates).
 *   The <- of a sub-view goes before the icon.
 * - RIGHT, one .topbar-actions / .header-actions: what you DO, in .tb-group
 *   boxes, in this order: the actions on a selection or the open item (only
 *   while there is one), then the search magnifier, + new, the app's other
 *   tools, then .tb-sys: more (...), settings, help (?). A thin line between
 *   two groups that show, drawn here - never a hand-placed .sep.
 *   Buttons with data-rank leave when the bar runs out of room (highest rank
 *   first, .fit-out) and the ONE ⋮ in .tb-sys lists them (NayiveUI.fitBar in
 *   shared/browser.js): the bar stays on the title's line.
 *   A selection group that comes and goes grows the bar leftwards, so the
 *   buttons after it never move.
 * - .topbar is the tool-shell header. It is ONE row while everything fits; when
 *   the viewport is too narrow the button bar wraps onto the next line(s) - the
 *   same way a toolbar overflows - and the title always stays fully readable
 *   (it wraps its own text before it is ever clipped). Divider underneath.
 *   Card-shell apps (.header) LOOK the same (his call, 2026-09-30): the card's
 *   top row, edge to edge, same padding, same divider, sticky while the list
 *   scrolls under it. See "Card-shell header" below.
 * - .app-icon: an inline SVG in --accent, 20px in either header.
 * - The <h1> (or .app-title) is ALWAYS --accent, 1.1rem, weight 700, no margin.
 *   Do not colour it --text; do not size it per app. Never give it
 *   `text-overflow: ellipsis` - the title is not allowed to be truncated.
 * - The sync dot is ALWAYS the last child of the header.
 * - Header buttons are .icon-btn (see below). The primary "new" one gets
 *   .accent; a destructive one gets .danger.
 * - Wrap the trailing controls (every button + the sync dot) in a
 *   .topbar-actions / .header-actions group so the whole bar drops to the next
 *   line together, instead of one lone button clinging to the title's line.
 *   A header with just a control or two can skip the wrapper.
 * ---------------------------------------------------------------------------*/
.topbar
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;              /* a long title / crowded bar wraps to a second row, never truncates */
    gap: 2px;
    row-gap: 4px;                 /* breathing room between wrapped rows */
    flex: 0 0 auto;
    padding: 8px 10px;
    border-bottom: 1px solid var(--line);
    overflow-x: auto;            /* safety net: a single un-wrappable wide child still scrolls */
}

.topbar h1,
.app-title
{
    flex: 0 1 auto;             /* sit at its natural width; wrap its text before shrinking further */
    min-width: min-content;    /* wrap the title's own text - never clip it */
    margin: 0 10px 0 0;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--accent);
    white-space: normal;
}

.app-icon          { flex: 0 0 auto; color: var(--accent); }
/* .topbar's gap is 2px (a toolbar's), so the icon needs 6px more to sit the same
   8px from the title as it does in a card .header, whose gap is --space-2.
   Descendant, not child: Drive wraps its icon + title in a div. */
.topbar .app-icon  { margin-right: 6px; }

/* The app icon + name double as a button back to the launcher (wired in
   shared/ui.js). Only the cursor + a light hover hint at it - the layout is
   unchanged. Real controls inside the header keep their own action. */
.nayive-home-link                    { cursor: pointer; }
.topbar h1.nayive-home-link:hover,
.header h1.nayive-home-link:hover,
.app-title.nayive-home-link:hover    { text-decoration: underline; text-underline-offset: 3px; }
.app-icon.nayive-home-link:hover     { opacity: 0.7; }

/* Desktop mode: a window behind others (html[data-win-behind], set by the
   desktop) shows its icon + name in the same colour, softer. */
html[data-win-behind] .topbar .app-icon,
html[data-win-behind] .header .app-icon,
html[data-win-behind] .topbar h1,
html[data-win-behind] .header h1,
html[data-win-behind] .trip-header .app-icon,
html[data-win-behind] .app-title     { opacity: 0.55; }

/* Chat has unread messages (shared/ui.js, CHAT DOT): a red dot on the app
   icon's top-right corner, placed by JS. It lets the tap through to the icon.
   It blinks 5 times when a new message arrives - on purpose also with
   "reduce motion" on: it is a short signal, not decoration. */
.chat-dot
{
    position: absolute; z-index: 2;
    width: 11px; height: 11px; border-radius: 50%;
    background: var(--danger);
    box-shadow: 0 0 0 2px var(--bg);
    pointer-events: none;
}
.chat-dot[hidden]  { display: none; }
.chat-dot.blink    { animation: chat-dot-blink .7s ease-in-out 5; }
@keyframes chat-dot-blink { 50% { opacity: 0.1; transform: scale(0.6); } }

/* -----------------------------------------------------------------------------
 * EMBEDDED  - the page runs inside another Nayive page's <iframe> (Planner
 * frames calendar + tasks + habits). shared/ui.js stamps html.is-embedded
 * on load. The host page already shows the app name and ONE sync dot for both
 * frames, so the framed app hides its own icon, title and dot. Its toolbar
 * buttons (and its "?" help, which documents THAT toolbar) stay.
 * ---------------------------------------------------------------------------*/
html.is-embedded .topbar > .app-icon,
html.is-embedded .header > .app-icon,
html.is-embedded .topbar > h1,
html.is-embedded .header > h1,
html.is-embedded .sync-indicator         { display: none; }
/* card-shell header (tasks): the <h1> was the spacer that pushed the buttons
   to the right edge; keep them there without it */
html.is-embedded .header                 { justify-content: flex-end; }

/* ...EXCEPT the icon when the HOST is a PC / tablet (html.is-embedded-wide, see
   shared/ui.js): there Planner shows the three panes at once, and the icon in
   the top-left corner of each one says which app that area is. The title stays
   off - the icon alone is the label, and a 25%-wide pane has no room for more.
   On a phone Planner shows one pane at a time from three icon buttons in its
   own header, so the class is absent and the rule above still hides the icon.

   Nothing has to move to make room: calendar's .topbar starts with the icon,
   tasks' always-open compose field (flex-grow) still pushes the buttons to the
   right edge, and habits' .tabs already carry margin-right: auto. One detail
   though (the icon is 20px in every header now, so no trim is needed; habits'
   header, already the fullest row, wraps its buttons to a second line in a 25%
   pane either way): the header packs from the LEFT again: the flex-end above was written for a header
   that began with a button, and it parked the icon at the right end of its row
   the moment the row wrapped. */
html.is-embedded.is-embedded-wide .topbar > .app-icon,
html.is-embedded.is-embedded-wide .header > .app-icon { display: block; }
html.is-embedded.is-embedded-wide .header             { justify-content: flex-start; }

/* A framed card app (tasks, habits) fills its pane. The card shell centres a
   460px sheet of paper on a wide screen, which is right when the app IS the
   window; inside a Planner pane the pane already is the app's whole area, so
   the sheet look would only add gutters and a second border around a box that
   already has one. Same treatment as the <=480px phone rule further up -
   no outer padding, no cap, no border / radius / shadow, and a min-height so a
   short list does not leave a card-coloured box floating on the page
   background. Standalone (own tab, installed PWA) the card is untouched. */
html.is-embedded .page,
html.is-windowed .page                       { padding: 0; }
html.is-embedded .page > .page-inner,
html.is-windowed .page > .page-inner         { max-width: 100%; }
html.is-embedded .page > .page-inner > .card,
html.is-windowed .page > .page-inner > .card
{
    min-height: 100vh;
    border: none;
    border-radius: 0;
    box-shadow: none;
}

/* -----------------------------------------------------------------------------
 * WINDOWED  - the app runs as a window of the desktop (desktop/index.html).
 * shared/ui.js stamps html.is-windowed instead of .is-embedded: the app keeps
 * its icon and title (not its sync dot: the desktop shows one for all), and its top row doubles as the window's title
 * bar. The card shell fills the window (same rule as EMBEDDED above). The
 * window's own buttons (Scale / Resize, minimise, maximise, close) are on the
 * desktop's bar, not in the app.
 * ---------------------------------------------------------------------------*/
/* The connection status: ONE for the whole desktop, after its clock (it copies
   the worst of every window's), so no window shows its own. */
html.is-windowed .sync-indicator { display: none; }

/* The trailing button bar, kept together so it wraps as one unit (see above). */
.topbar-actions,
.header-actions
{
    display: flex;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
    margin-left: auto;          /* hug the right edge while it shares the title's line */
}

/* The right-hand groups (see HEADER). A group with nothing shown takes no room,
   and the line before a group only draws when a group before it shows. */
.tb-group                                   { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.tb-group[hidden],
.tb-group:not(:has(> :not([hidden]):not(.fit-out)))   { display: none; }
:is(.topbar-actions, .header-actions, .ie-bar) > .tb-group:not([hidden]):has(> :not([hidden]):not(.fit-out)) ~ .tb-group::before
{
    content: "";
    align-self: stretch;
    width: 1px;
    margin: 6px 4px;
    background: var(--line);
}

/* In a desktop window the top row is the window's title bar (desktop/index.html
 * drags it from any empty spot). The app name keeps an empty strip after it to
 * grab the window by - 1.5rem - so every
 * control after it stays together. Drive and Planner wrap their name, so the
 * wrapper carries the strip there (drive.css, planner/index.html). */
html.is-windowed .topbar h1,
html.is-windowed .header h1,
html.is-windowed .app-title { margin-right: 1.5rem; }

/* Card-shell header (Contacts, Tasks, Habits, Split): the same bar as a tool
   app's .topbar (his call, 2026-09-30) - same padding, gaps, icon size and
   divider - laid over the card's top padding, edge to edge. It never scrolls
   away: sticky, opaque, the rows slide under it unseen (in a desktop window it
   is also the title bar). The floating sync chip lands under its divider, as
   it does in every other app. shared/ui.js puts its height in --win-head-h for
   an app's own sticky heads (Contacts' letters). */
.header
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px;
    row-gap: 4px;
    margin-bottom: var(--space-4);
}
.page > .page-inner > .card > .header
{
    position: sticky; top: 0; z-index: 20;
    margin: -20px -16px var(--space-4);          /* the card's padding: 20px 16px */
    padding: 8px 10px;
    border-bottom: 1px solid var(--line);
    border-top-left-radius: inherit;             /* the card's own corners - none on a phone */
    border-top-right-radius: inherit;
    background: var(--card);
}
.header .app-icon  { width: 20px; height: 20px; margin-right: 6px; }
.header h1,
.header .app-title
{
    flex: 1 1 auto;
    min-width: min-content;     /* wrap the title, never clip it; buttons wrap below instead */
    margin: 0 10px 0 0;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--accent);
    white-space: normal;
}


/* -----------------------------------------------------------------------------
 * FOLDER-BACKED VIEWERS  (Photos, Music, Movies)  -  one header row
 *
 *   .topbar: [icon] <h1> [folder ?] [.tb-modes]  ...  .topbar-actions [ search, tools, rescan | ? ]  [sync dot]
 *
 * The toolbar lives IN the header, as in Drive - the header is also the
 * desktop window's title bar. It follows the HEADER order: folder-? and the
 * modes on the left, the rest in .topbar-actions. An app that shows no
 * tools yet (Music / Movies before a folder is picked) hides its .tb-tools
 * boxes (display: contents).
 *
 * - The folder-? button (.icon-btn.has-popup) opens a .top-menu.folder-menu
 *   (NayiveMedia.wireFolderInfo): #crumb, a .menu-item with the folder icon +
 *   the folder PATH that opens the change-folder picker, then #subStrip, a
 *   .menu-info line with the item counts / genres / totals. Nothing else
 *   shows the path or the counts.
 * - .tb-modes holds the buttons that switch what is shown; the right-hand
 *   group starts with the search (it folds behind a magnifier, SEARCH FOLD
 *   below), then the app's tools, then the rescan button (#reloadBtn in all
 *   three); help is in the .tb-sys group after it.
 * - The games still use .view-bar as a row of its own (below).
 * ---------------------------------------------------------------------------*/
.tb-tools:not([hidden]) { display: contents; }
/* The .topbar's gap is a toolbar's 2px; its groups keep the 8px the old row had. */
.topbar .tb-modes,
.topbar .tb-actions     { margin-right: 6px; }

.folder-menu            { max-width: min(360px, calc(100vw - 12px)); }
.folder-menu #crumb span { overflow-wrap: anywhere; }
.menu-info
{
    padding: 4px 12px 6px;
    font-size: 0.8rem;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.menu-info:empty        { display: none; }

/* A row of toolbar groups (the games' option bars). .tb-modes switches WHAT is
   shown, .tb-actions acts ON it (introduced by a .tb-sep). The
   action group carries its own divider inside it, so a group whose buttons are
   all hidden takes the divider with it instead of leaving it hanging. */
.view-bar
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    row-gap: 2px;
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--line);
}
.tb-modes,
.tb-actions       { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.tb-actions:not(:has(> button:not([hidden]))) { display: none; }

/* Scoped, NOT a bare .tb-sep: text uses that name for a vertical divider of its
   own and write for a HORIZONTAL one inside .tb-popup - a global width: 1px
   would squash write's to a sliver. */
.view-bar .tb-sep,
.tb-actions > .tb-sep { width: 1px; align-self: stretch; margin: 4px; background: var(--line); flex: 0 0 auto; }

/* -----------------------------------------------------------------------------
 * SEARCH FOLD  - every toolbar search field (NayiveUI.searchFold, ui.js)
 *
 * At rest only the magnifier (.search-fold-toggle) shows. Opened, the field
 * (.search-fold.is-open) stands in its place and takes the room the bar has
 * left, up to 360px; the bar (.searching) stops wrapping so "too narrow" can
 * be measured. When the field would get under 180px the bar turns
 * .search-tight and its other icons step aside. Inside the field, right: a
 * funnel button (.search-mark, only for a field with a filter dialog:
 * .has-filter), an up / down pair (.search-step, a field that steps through
 * its hits in place: .has-steps), then the × (.search-shut) that folds it away. The field's
 * look is the shared input rule - see FORM FIELD below.
 * ---------------------------------------------------------------------------*/
.search-fold                { display: none; position: relative; flex: 1 1 auto; width: auto;
                              min-width: 0; max-width: 360px; }
.search-fold.is-open        { display: block; }
.search-fold > input        { width: 100%; padding-right: 34px; }
/* type="search" gets the browser's own × while typing (Chromium, Safari):
   the fold's × already does that job - hide it so no app shows two. */
.search-fold > input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; display: none; }
.search-fold.has-filter > input { padding-right: 60px; }
.search-fold.has-steps > input  { padding-right: 86px; }   /* up · down · × */
.search-fold .search-ends   { position: absolute; top: 0; bottom: 0; right: 2px;
                              display: flex; align-items: center; }
.search-fold .search-ends .icon-btn.sm     { width: 26px; height: 26px; }
.search-fold .search-ends .icon-btn.sm svg { width: 15px; height: 15px; }
/* How many items match (fold.count), before the funnel and the ×. */
.search-fold .search-count  { padding: 0 4px; font-size: 0.75rem; color: var(--text-dim);
                              white-space: nowrap; font-variant-numeric: tabular-nums; pointer-events: none; }
.search-fold .search-count[hidden] { display: none; }
/* Filters in force (the app sets .filter-on): the funnel lit. */
.search-fold.filter-on .search-mark { color: var(--accent); }
.search-fold-bar.searching  { flex-wrap: nowrap; }
.searching .search-fold-toggle { display: none; }
/* The group holding the field grows with it: the header's buttons stay at
   the right edge, the view bar's right after the field. */
.topbar-actions:has(.search-fold.is-open),
.header-actions:has(.search-fold.is-open),
.tb-group:has(> .search-fold.is-open)       { flex: 1 1 auto; min-width: 0; justify-content: flex-end; }
/* A card .header's title grows at rest (it pushes the buttons right); while
   searching it keeps its width, as a .topbar's does, and the field gets the room. */
.header.searching h1        { flex-grow: 0; }
.search-tight .icon-btn:not(.search-fold .icon-btn),
.search-tight .sep,
.search-tight .tb-sep,
.search-tight .tb-group::before { display: none; }


/* -----------------------------------------------------------------------------
 * GROWING WASH  - the hover of EVERY icon-only button in every app
 *
 * .icon-btn, .pop-btn, .transport-btn, the sync button, the toolbar buttons of
 * Calc (.fmt-btn), Text (.tb-btn) and Write (#toolbarRow .icon-btn) and Chat's
 * emoji grid. Nothing at rest; on hover a round --accent 14% wash grows from
 * the centre to full size; pressed --accent 24%. One rule, so the look never
 * drifts: a button's own rule sets its size and shape (round), never its
 * background, except a state that must win (.is-active, :disabled, .danger).
 * ---------------------------------------------------------------------------*/
.icon-btn, .pop-btn, .transport-btn, button.sync-indicator,
.tb-btn, .fmt-btn, .fmt-color-btn, .emojis button
{
    --ib-wash: color-mix(in srgb, var(--accent) 14%, transparent);
    background: radial-gradient(circle closest-side, var(--ib-wash) calc(100% - 1px), transparent) center / 0% 0% no-repeat;
    transition: background-size var(--tr-fast), color var(--tr-fast);
}
.icon-btn:hover, .pop-btn:hover, .transport-btn:hover, button.sync-indicator:hover,
.tb-btn:hover, .fmt-btn:hover, .fmt-color-btn:hover, .emojis button:hover
{
    background-size: 100% 100%;
    color: var(--text);
}
.icon-btn:active, .pop-btn:active, .transport-btn:active, button.sync-indicator:active,
.tb-btn:active, .fmt-btn:active, .fmt-color-btn:active, .emojis button:active
{
    --ib-wash: color-mix(in srgb, var(--accent) 24%, transparent);
    background-size: 100% 100%;
}


/* -----------------------------------------------------------------------------
 * ICON BUTTON  (.icon-btn)  - the workhorse control
 *
 * A round, borderless, icon-only button used in headers, toolbars and rows.
 *
 *   size        36px (use .icon-btn.sm = 30px for dense toolbars)
 *   shape       circle, no border
 *   rest        no background
 *   colour      --text-dim at rest, --text on hover
 *   hover       the GROWING WASH above (round --accent 14%)
 *   pressed     --accent 24%
 *   focus       a visible --accent ring for keyboard users
 *   disabled    opacity 0.45, no pointer, no hover
 *
 * The SVG inside: 24x24 viewBox, stroke="currentColor", fill="none",
 * stroke-width ~2, round caps and joins (Feather style). Rendered 18-20px.
 *
 * MODIFIERS
 *   .accent   solid --accent fill, --on-accent icon. The "new item" button.
 *   .danger   a DELETE button: a --danger icon at rest (not just on hover);
 *             hover adds a faint --danger wash. Borderless like every other
 *             .icon-btn - the red icon alone marks it. Matches the dialog
 *             .btn-danger so "delete" looks the same everywhere.
 *   .is-active  solid --accent fill for a toggle that is currently on
 *               (e.g. the calendar view switch).
 * ---------------------------------------------------------------------------*/
.icon-btn
{
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    padding: 0;
    border: none;
    border-radius: 50%;
    color: var(--text-dim);
    cursor: pointer;
}
.icon-btn svg           { width: 19px; height: 19px; display: block; }
.icon-btn:focus         { outline: none; }
.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.icon-btn:disabled      { opacity: 0.45; cursor: default; background: transparent; color: var(--text-dim); }

.icon-btn.sm            { width: 30px; height: 30px; }
.icon-btn.sm svg        { width: 17px; height: 17px; }

/* There is no accent variant of .icon-btn. The "new / add" button in Tasks,
 * Habits, Calendar, Contacts and Split is a plain one: same --text-dim glyph,
 * same hover as every other button in the row. Its icon says what it does.
 * (.danger below is the one colour variant, and it is a warning, not emphasis.) */

.icon-btn.danger        { color: var(--danger); }
.icon-btn.danger:hover  { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }

.icon-btn.is-active         { background: var(--accent); color: var(--on-accent); }
.icon-btn.is-active:hover   { background: var(--accent); }

/* A call's buttons (chat/call.js): .lg is the big round button of a call
 * screen, .solid-ok answers (a green fill), .solid-danger hangs up or
 * declines (a red fill) - the one place a phone-call convention beats the
 * "borderless, red icon only" rule of .danger above. */
.icon-btn.lg                { width: 60px; height: 60px; }
.icon-btn.lg svg            { width: 26px; height: 26px; }
.icon-btn.solid-ok,
.icon-btn.solid-ok:hover    { background: var(--ok); color: var(--on-solid); }
.icon-btn.solid-danger,
.icon-btn.solid-danger:hover { background: var(--danger); color: var(--on-solid); }
.icon-btn.solid-ok:active,
.icon-btn.solid-danger:active { filter: brightness(0.9); }

/* TOOLBAR BUTTON - a related but distinct component, NOT styled here.
 * calc (.fmt-btn), text (.tb-btn) and write (#toolbarRow .icon-btn) each define
 * their own size: a 30-32px circle, --text-dim icon. Hover and press are the
 * GROWING WASH above, like every icon button. It stays per-app on
 * purpose - the three toolbars differ enough that a shared class earns nothing.
 * If you add a fourth, copy one of the existing three. */


/* -----------------------------------------------------------------------------
 * TEXT BUTTON  (.text-btn)  - a wide call to action
 *
 * Use when a button needs a word, not just an icon: "Añadir", "Importar",
 * "Guardar cambios" on a full-width row. Accent fill, --on-accent text.
 * For a dialog's confirm / cancel row use the round .btn set from theme.css
 * instead (see DIALOG BUTTONS below).
 *
 *   .text-btn.ghost   quiet version: no fill, --line border, --text-dim text.
 *   .text-btn.ghost.danger  the quiet version of a destructive action ("Vaciar
 *                     papelera"): --danger text and a --danger-tinted border.
 *   .text-btn.dashed  an "add another" affordance: dashed accent border on a
 *                     faint accent wash (trip's "Añadir etapa").
 *   .text-btn.small   tighter, for a secondary action in a dialog's button row.
 * ---------------------------------------------------------------------------*/
.text-btn
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px 16px;
    border: none;
    border-radius: var(--radius-s);
    background: var(--accent);
    color: var(--on-accent);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: filter var(--tr-fast), background-color var(--tr-fast);
}
.text-btn:hover     { filter: brightness(1.08); }
.text-btn:active    { filter: brightness(0.94); }
.text-btn:disabled  { opacity: 0.45; cursor: default; filter: none; }
.text-btn svg       { width: 16px; height: 16px; flex: 0 0 auto; }

.text-btn.ghost
{
    background: transparent;
    color: var(--text-dim);
    border: 1px solid var(--line);
}
.text-btn.ghost:hover { background: color-mix(in srgb, var(--text) 9%, transparent); color: var(--text); filter: none; }
.text-btn.ghost.danger,
.text-btn.ghost.danger:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }

.text-btn.dashed
{
    width: 100%;
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    color: var(--accent);
    border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
}
.text-btn.dashed:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); filter: none; }
.text-btn.small  { padding: 8px 12px; font-size: 0.82rem; }     /* a secondary action inside a dialog row */


/* -----------------------------------------------------------------------------
 * DIALOG BUTTONS  (.sheet-actions / .btn / .btn-*)  - defined in theme.css
 *
 * The bottom row of a dialog is a right-aligned line of round 44px icon-only
 * buttons. Mark them up declaratively and let shared/ui.js fill in class + SVG:
 *
 *     <div class="sheet-actions">
 *       <button data-act="close"   title="Cancelar"></button>
 *       <button data-act="primary" title="Guardar"></button>
 *     </div>
 *
 * Roles: close (X) - primary (check) - danger (trash) - ghost (help).
 * .sheet-actions-left pins a delete button to the far left.
 *
 * ONE ICON PER MEANING (dialogs and toolbars alike):
 *   check   = confirm / apply / save / rename / create - always, any verb.
 *   saveas  = "save as a new copy" (check + a small +). Nothing else.
 *   trash   = a destructive confirm. Never looks like a save.
 *   A different verb keeps its own glyph: edit (opens a form), search (runs a
 *   query), plus (a compose bar).
 *
 * A dialog whose only action is "close" has NO bottom row - ui.js moves the
 * lone close button to the top-right corner, on the title's line, as
 * .sheet-close.
 * ---------------------------------------------------------------------------*/


/* -----------------------------------------------------------------------------
 * DIALOG / SHEET  (.sheet-backdrop > .sheet)
 *
 *     <div class="sheet-backdrop" id="fooBackdrop">
 *       <div class="sheet">
 *         <h2>Título</h2>
 *         ... .field rows ...
 *         <div class="sheet-actions"> ... </div>
 *       </div>
 *     </div>
 *
 * - The backdrop is a fixed --scrim layer; add .open (or NayiveUI.open(id)) to
 *   show it. It centres the sheet on desktop.
 * - The sheet is a --card panel, --radius-l corners, --elev-dialog shadow,
 *   scrolls inside at tall content. Default max-width 460px; add .sheet--wide
 *   for 560px when the content is genuinely wide (a table, a wide form).
 * - .sheet--pack shrinks the dialog to the width its content actually needs
 *   (clamped to [300px, 460px] and to the screen). NayiveUI.setOpen() runs the
 *   fit on open; NayiveUI.pack(el, {min,max}) does it by hand with custom bounds.
 *   Use it for short forms (a few selects / a checkbox) that look lost at 460px.
 * - CLOSING: only the × / ✓ buttons and the Esc key close a dialog. Never wire
 *   a click on the backdrop to close - it loses half-typed input.
 * - The backdrop is a flex box that centres the sheet both ways, so every
 *   dialog sits in the middle of the visible area on any screen size.
 * - MOVABLE: on PC / tablet the dialog can be dragged anywhere on screen by
 *   pressing a free spot of it (not a control) - shared/ui.js does this for
 *   every .sheet, there is nothing to add to a dialog. It always reopens
 *   centred. Phones (<= 640px) never move a dialog.
 * - FOCUS: on open, shared/ui.js puts the caret in the dialog's FIRST field
 *   (input / select / textarea / contenteditable) - again for every .sheet,
 *   nothing to add. It never focuses a button, and it leaves alone any focus
 *   the app set itself, so a dialog that wants another field focused just
 *   focuses it when it opens. Fields that only appear after a fetch are waited
 *   for. Write the fields in the order they should be filled in.
 * ---------------------------------------------------------------------------*/
.sheet-backdrop
{
    position: fixed;
    inset: 0;
    z-index: 100;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: var(--scrim);
}
.sheet-backdrop.open { display: flex; }

.sheet
{
    position: relative;
    width: 100%;
    max-width: 460px;
    max-height: 92vh;
    max-height: 92dvh;            /* dvh: the visible height once iOS's URL bar is accounted for */
    overflow-y: auto;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--elev-dialog);
}
.sheet--wide       { max-width: 560px; }

/* Content-fit width. JS (NayiveUI) sets an exact px width on open; this is the
 * pre-JS / fallback look so the sheet never flashes at the full 460px first. */
.sheet--pack       { width: max-content; min-width: 300px; }

/* MOVING A DIALOG (shared/ui.js). The whole dialog is the handle - press on any
 * free spot and drag - so there is nothing to draw until a drag really starts.
 * Then: the "held" cursor everywhere, and text selection off so the pointer
 * does not paint the dialog blue on its way across. The sheet is moved with an
 * inline `transform: translate(...)`, which is dropped when it closes. */
.sheet.is-dragging,
.sheet.is-dragging *
{
    cursor: grabbing;
    user-select: none;
    -webkit-user-select: none;
}

.sheet h2
{
    margin: 0 0 14px;
    font-size: 1.15rem;
    font-weight: 700;
}

/* Body copy in a NayiveUI.confirm / NayiveUI.alert sheet (shared/ui.js). */
.sheet > p.dialog-text
{
    margin: 0 0 14px;
    color: var(--text-dim);
    line-height: 1.45;
    white-space: pre-wrap;
}
.sheet > p.dialog-text:last-of-type { margin-bottom: 4px; }

/* Properties / Info dialog (Drive, Photos): a label / value grid. The dimmed
   label reads like a .field label; the exact byte count goes under its own
   value in small type, the way every file manager writes it. */
.props
{
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 8px 16px;
    margin: 0 0 14px;
    font-size: 0.9rem;
}
.props dt { color: var(--text-dim); }
.props dd { margin: 0; overflow-wrap: anywhere; }
.props dd .props-sub { display: block; font-size: 0.8rem; color: var(--text-dim); }

/* A dialog header row: a title on the left, small controls on the right (an
 * "all day" switch, a step counter; .sheet-tool buttons, then the ×). The
 * title takes the room, so a long one wraps instead of running under them.
 * The × sits as near the corner as when it was pinned there, and its 32px
 * disc does not make the line taller than the title. */
.sheet-header
{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 2px 0 16px;
}
.sheet-header h2   { margin: 0; flex: 1 1 auto; min-width: 0; }
.sheet-header > .sheet-close,
.sheet-header > .sheet-tool  { margin: -6px 0; }
.sheet-header > .sheet-close:last-child { margin-right: -6px; }
.sheet-header > .sheet-tool + .sheet-tool,
.sheet-header > .sheet-tool + .sheet-close { margin-left: -8px; }

/* SWITCH - an on / off slider: <label class="switch"><input type="checkbox">
 * <span class="track"></span></label>, or the same as a <span> inside a label.
 * Every on / off option is one (2026-09-30); a tick box is only for picking
 * items from a list. inline-block so its size holds outside a flex row; the
 * input is out of flow, or the checkbox / .field width rules below give it room. */
.switch { position: relative; display: inline-block; width: 44px; height: 26px; flex: 0 0 auto; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.switch .track { position: absolute; inset: 0; background: rgba(128,128,128,0.35); background: color-mix(in srgb, var(--text) 22%, transparent); border-radius: var(--radius-pill); transition: background .15s; cursor: pointer; }
.switch .track::before { content: ''; position: absolute; width: 20px; height: 20px; left: 3px; top: 3px; background: var(--on-solid); border-radius: 50%; transition: transform .15s; box-shadow: 0 1px 2px rgba(0,0,0,0.3); }
.switch input:checked + .track { background: var(--accent); }
.switch input:checked + .track::before { transform: translateX(18px); }
.switch input:focus-visible + .track { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch input:disabled + .track { cursor: default; opacity: 0.5; }
/* .switch.sm - the shorter one, and the one every on / off row uses (2026-09-30). */
.switch.sm { width: 32px; height: 18px; }
.switch.sm .track::before { width: 14px; height: 14px; left: 2px; top: 2px; }
.switch.sm input:checked + .track::before { transform: translateX(14px); }

@media (max-width: 640px)
{
    /* Still a centred card on a phone - just let it use the full width and a
     * touch more height. The backdrop keeps centring it vertically. */
    .sheet { max-height: 90vh; max-height: 90dvh; }
}


/* -----------------------------------------------------------------------------
 * FORM FIELD  (.field)
 *
 *     <div class="field">
 *       <label for="x">Etiqueta</label>
 *       <input id="x" type="text">
 *     </div>
 *
 * - Label: 0.8rem, --text-dim, sentence case (NOT uppercase - that style is
 *   reserved for .section-label).
 * - Inputs, selects and textareas share one look: --card2 fill, --line border,
 *   --radius-s, 7px 10px padding, the page font at --field-fs (theme.css), no
 *   default outline. That is EVERY text field in EVERY app - see SPECIFICITY
 *   below.
 * - Focus shows a slightly stronger border (--text 30%). No glow, no colour
 *   change - the same treatment on every field in every app.
 * - Put two fields side by side by wrapping them in .field-row.
 *
 * SPECIFICITY: the input rules below are deliberately kept at their weakest
 * (`input, select, textarea`), so ANY rule an app already has - a
 * `.loc-input-wrap input`, a `.topbar-search` - still wins. That weakness is
 * for LAYOUT (width, flex, a padding-right that clears an inline button), NOT
 * for a second look: there is ONE text-field look in Nayive and it is the one
 * below. A toolbar search, a header compose field, a dialog field and a table
 * cell field are all the same --field-fs / 7px 10px box. If you find an app
 * setting its own padding, font-size or height on a field, delete it.
 *
 * There are exactly THREE exceptions, and all three are shared classes below -
 * an app still never rolls its own:
 *   .field-edit  (EDIT IN PLACE)   a click-to-rename box that has to sit exactly
 *                                  where a line of text was, so it runs a
 *                                  tighter 3px 7px.
 *   .tb-select   (TOOLBAR SELECT)  a drop-down that lives in a toolbar row, so
 *                                  it is button-height and transparent, not a
 *                                  filled field.
 *   .tb-input    (TOOLBAR INPUT)   the same, for a text box that lives in a
 *                                  toolbar row (Calc's name box and formula bar).
 * ---------------------------------------------------------------------------*/
.field             { margin-bottom: 14px; }

.field label
{
    display: block;
    margin-bottom: 5px;
    font-size: 0.8rem;
    color: var(--text-dim);
}

/* An on / off row: the text, then the shared .switch at the end (Calc, Write). */
.field label.check
{
    display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 0;
    font-size: 0.9rem; color: var(--text); cursor: pointer;
}

.field-row
{
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
.field-row .field  { flex: 1 1 120px; min-width: 0; }

/* What a field got wrong, right under it: "the two passwords do not match".
 * Hidden until there is something to say ([hidden] wins over the display). */
.field-error
{
    margin: -8px 0 12px;
    color: var(--danger);
    font-size: 0.8rem;
    line-height: 1.35;
}

/* PASSWORD EYE: a password box with a show / hide eye inside its right end
 * (login.html, admin.html, the launcher's My account). Each page adds only
 * what differs: the eye's width, the icon size, the field's room and margins. */
.pw-wrap           { position: relative; }
.pw-eye
{
    position: absolute; top: 0; right: 0; height: 100%;
    width: 38px; display: flex; align-items: center; justify-content: center;
    border: none; background: none; padding: 0; cursor: pointer;
    color: var(--text-dim);
}
.pw-eye:hover      { color: var(--text); }
.pw-eye svg        { width: 18px; height: 18px; }

/* A field's control fills the row. Scoped to .field so it never stretches a
 * toolbar / search input that lives outside a form. */
.field input,
.field select,
.field textarea    { width: 100%; }

input,
select,
textarea
{
    padding: 7px 10px;
    font: inherit;
    font-size: var(--field-fs);
    color: var(--text);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    outline: none;
}
textarea           { resize: vertical; min-height: 54px; }

/* A <select> is given ~2px more inner box than an <input> of the same font, so
 * the identical 7px above would leave it standing 2px taller than the field
 * next to it in a .field-row. One pixel off the top and bottom lines the two up
 * exactly - measured in Chromium at the field size and at the phone's 16px,
 * gap is the same 2px both times, so this is a constant and not a magic number
 * tuned to one size. */
select             { padding-block: 6px; }

input:focus,
select:focus,
textarea:focus     { border-color: color-mix(in srgb, var(--text) 30%, transparent); }

::placeholder      { color: var(--text-faint); }

/* The drop-down list a <select> opens is painted by the OS, and left alone it
 * comes back white-on-white in the dark theme. Tokenised here so a dialog select
 * in Calendar / Split / Write opens the same list as a toolbar one. The closed
 * box keeps the platform chevron - that is the same on every select, so it stays
 * native rather than being redrawn per app. */
select option      { color: var(--text); background: var(--card2); }

/* Browser autofill paints its own pale background + a hard border. Force the
 * themed look back. Chrome/Edge: the inset box-shadow is the only way to cover
 * the autofill background. Firefox: honours background-color on :autofill. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus
{
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 100px var(--card2) inset;
    caret-color: var(--text);
    border: 1px solid var(--line);
}
input:autofill { background-color: var(--card2); color: var(--text); }

/* Checkbox / radio / colour / file / range keep the browser's box - the field
 * look above is for text-like inputs only. */
input[type="checkbox"],
input[type="radio"],
input[type="color"],
input[type="file"],
input[type="range"]
{
    width: auto;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
    accent-color: var(--accent);
}


/* -----------------------------------------------------------------------------
 * EDIT IN PLACE  (.field-edit-text / .field-edit)
 *
 *     <span  class="field-edit-text is-editable">Comprar pan</span>   <!-- rest -->
 *     <input class="field-edit" type="text" value="Comprar pan">   <!-- editing -->
 *
 * The ONE sanctioned second field look, and the only exception to "one text
 * field look" above. It is not a size variant - it is a MODE: a line of text
 * you click to rename in place. Used by the document name in Calc / Text /
 * Write and by the task and sub-task rows in Tasks.
 *
 * The two rules share the same box - same padding, same 1px border (transparent
 * at rest), same radius, same --field-fs - so swapping span for input does
 * not move a pixel and the row never jumps. That is the whole point; if you
 * change the padding, change it in BOTH.
 *
 * The accent border is the only difference, and it is the "you are editing now"
 * signal. Everything else - fill, radius, no outline - is the standard field.
 *
 * Add .is-editable for the click affordance (text cursor + hover fill). It is a
 * separate class because a row can be shown but not renameable - a finished task
 * keeps the box, and loses the invitation to click it.
 *
 * Apps set flex / min-width / margin / colour on top. They do NOT set
 * font-size, padding or height: an edit box is the same size in every app.
 * ---------------------------------------------------------------------------*/
.field-edit-text
{
    padding: 3px 7px;
    font-size: var(--field-fs);
    line-height: 1.4;
    border: 1px solid transparent;
    border-radius: var(--radius-s);
    cursor: default;
}
.field-edit-text.is-editable        { cursor: text; }
.field-edit-text.is-editable:hover  { background: color-mix(in srgb, var(--text) 9%, transparent); }

.field-edit
{
    padding: 3px 7px;
    font: inherit;
    font-size: var(--field-fs);
    line-height: 1.4;
    color: var(--text);
    background: var(--card2);
    border: 1px solid var(--accent);
    border-radius: var(--radius-s);
    outline: none;
}


/* -----------------------------------------------------------------------------
 * TOOLBAR SELECT  (.tb-select)  -  Calc's format bar, Text's editor bar
 *
 *     <select class="tb-select">…</select>
 *
 * The second and last exception to the one field look. A drop-down that sits in
 * a toolbar row, not in a form: it takes the row's 30px button height and stays
 * transparent, so it reads as one of the toolbar's controls instead of a filled
 * field parked in the chrome. 0.82rem is the toolbar's own type size.
 *
 * It keeps a 1px --line outline where the round .icon-btn next to it has none -
 * a drop-down has to look pressable and show where it ends. Its hover is the
 * accent wash Text's .tb-btn uses, not .icon-btn's grey.
 *
 * Give it a width from the app (.fmt-select-font, .fmt-select-narrow ...): how
 * wide a font list needs to be is the app's business, the look is not.
 * ---------------------------------------------------------------------------*/
.tb-select
{
    height: 30px;
    flex: 0 0 auto;
    padding: 0 4px;
    font: inherit;
    font-size: 0.82rem;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    cursor: pointer;
    outline: none;
}
.tb-select:hover   { background: color-mix(in srgb, var(--accent) 14%, transparent); }

/* The same control as a number box (Text's right-margin spinner): every
 * property restated, to beat the filled FORM FIELD look every <input> gets.
 * It keeps the browser's own up / down arrows - that is what makes it a
 * spinner. */
.tb-num
{
    width: 4.6em;
    height: 30px;
    flex: 0 0 auto;
    padding: 0 0 0 6px;
    font: inherit;
    font-size: 0.82rem;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    outline: none;
}
.tb-num:hover      { background: color-mix(in srgb, var(--accent) 14%, transparent); }


/* -----------------------------------------------------------------------------
 * TOOLBAR INPUT  (.tb-input)  -  Calc's name box and formula bar
 *
 *     <input class="tb-input" type="text">
 *
 * The third exception to the one field look, and the twin of .tb-select: a text
 * box that sits in a toolbar row instead of in a form. Same 30px button height,
 * same 1px --line outline, same 0.82rem type.
 *
 * Unlike the drop-down it stays a FILLED box - it is written into, and a
 * transparent one reads as a label rather than something you can type in - and
 * its focus shows in the accent, as every field in Nayive does.
 *
 * Give it a width from the app (Calc's name box is 88px, its formula bar takes
 * the rest of the row): how wide the box has to be is the app's business.
 * ---------------------------------------------------------------------------*/
.tb-input
{
    height: 30px;
    flex: 0 0 auto;
    padding: 0 8px;
    font: inherit;
    font-size: 0.82rem;
    color: var(--text);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    outline: none;
}
.tb-input:focus    { border-color: var(--accent); }


/* -----------------------------------------------------------------------------
 * PILL  (.pill)  and  TAG  (.tag)
 *
 * .pill  - a small rounded button, used for filters and mode switches. At rest
 *          it is a quiet --card2 chip; add .is-active for the accent state.
 * .tag   - the same shape but read-only (a metadata chip on a card). No hover,
 *          no active state, default cursor.
 * ---------------------------------------------------------------------------*/
.pill
{
    padding: 7px 14px;
    font-family: inherit;
    font-size: 0.77rem;
    color: var(--text-dim);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: opacity var(--tr-fast), background-color var(--tr-fast);
}
.pill:hover        { opacity: 0.85; }
.pill.is-active
{
    color: var(--on-accent);
    background: var(--accent);
    border-color: var(--accent);
}
/* A settings dialog's tabs: a row of .pill buttons over its panes
   (Trips, Chat). */
.set-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 16px; }
/* A page's own row of .pill tabs (Habits, Split): the margin is the page's. */
.tabs     { display: flex; flex-wrap: wrap; gap: 6px; }

.tag
{
    padding: 4px 10px;
    font-size: 0.74rem;
    color: var(--text-dim);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
}


/* -----------------------------------------------------------------------------
 * CARD ROW  (.card-row)
 *
 * A row in a list - a file in Drive, a trip, a saved document. A --card2
 * surface with --line border and --radius-m corners. Hover firms up the
 * border; .is-selected fills it with accent and flips the text.
 * ---------------------------------------------------------------------------*/
.card-row
{
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    color: var(--text);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    transition: border-color var(--tr-fast), background-color var(--tr-fast);
}
.card-row:hover        { border-color: color-mix(in srgb, var(--text) 22%, transparent); }
.card-row.is-selected  { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }


/* -----------------------------------------------------------------------------
 * SECTION LABEL  (.section-label)
 *
 * A small uppercase heading over a group of fields or rows ("DOCUMENTOS",
 * "ETAPAS"). The one place uppercase + letter-spacing is used. Keep it short.
 * --text-faint, one step dimmer than an .icon-btn, so a label beside buttons
 * never reads as one of them.
 * ---------------------------------------------------------------------------*/
.section-label
{
    margin: 4px 2px 8px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
}


/* -----------------------------------------------------------------------------
 * INFO DOT  (.info-dot)  +  INFO POPUP  (.info-popup)
 *
 * A small circled "i" you put next to a label or a control to explain it. Tap
 * it and a little popup opens with the text; tap it again, tap anywhere else,
 * press Esc or scroll and it closes. It is a POPUP, not a dialog - it has no
 * "x", no backdrop, and never blocks the page.
 *
 * On a PC (a real mouse) it ALSO opens on hover, and stays open while the
 * pointer is on the dot or on the popup itself; a click pins it, so from there
 * on it behaves like on a phone. Touch screens are unchanged. See shared/ui.js.
 *
 *     <label for="tz">Zona horaria
 *       <button class="info-dot" data-info="Las horas se guardan en UTC y se
 *       muestran en tu zona."></button>
 *     </label>
 *
 * shared/ui.js fills in the icon + ARIA and wires the open/close (it runs on
 * load and watches for dots added later; call NayiveUI.applyInfoDots(root) after
 * building markup by hand). The text comes from `data-info`; "\n" starts a new
 * line. Keep it to a sentence or two - anything longer belongs in a dialog.
 *
 * COLOUR: --link (info-blue) for both the dot and the popup's border, so it
 * stays legible at 16px in both schemes and never reads as the green --accent
 * (reserved for active / primary). The dot goes --accent only on hover / while
 * its popup is open.
 * ---------------------------------------------------------------------------*/
.info-dot
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 16px;                /* one size for every (i) */
    height: 16px;
    padding: 0;
    margin: 0 0 0 4px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--link);        /* info-blue - readable at this size, not the green accent */
    vertical-align: middle;
    cursor: pointer;
    transition: color var(--tr-fast);
}
/* :root lifts it above any container's "<class> svg" rule (.menu-item svg
   made the menu's dots grey and smaller): a dot looks the same everywhere. */
:root .info-dot svg     { width: 16px; height: 16px; display: block; flex: 0 0 auto; color: inherit; }
/* A menu row already spaces its items (gap 10px): no extra margin, so the dot
   sits as close to its label as in the Drive bin bar (gap 6 + margin 4). */
.menu-item > .info-dot  { margin-left: 0; }
.info-dot:hover,
.info-dot.is-open       { color: var(--accent); }
.info-dot:focus         { outline: none; }
.info-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.info-popup
{
    position: absolute;
    z-index: 200;                /* above sheets (100), below nothing else */
    max-width: 260px;
    padding: 10px 12px;
    font-size: 0.82rem;
    line-height: 1.4;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--link);   /* info-blue, ties the popup to its dot */
    border-radius: var(--radius-m);
    box-shadow: var(--elev-dialog);
    white-space: pre-wrap;       /* honour "\n" in data-info */
}
.info-popup a { color: var(--link); }   /* an "https://..." in data-info (ui.js setInfoText) */


/* -----------------------------------------------------------------------------
 * SYNC INDICATOR + TOAST  - defined in theme.css
 *
 * .sync-indicator  the plug glyph in the header (shared/ui.js draws it). It is
 *                  hidden while all is well and floats as a chip just off the
 *                  bar's end when not (theme.css, FLOATING CHIP).
 *                  Toggle the state class:
 *     (none)     --danger red   not synced yet (the safe start state)
 *     .synced    --ok green     saved
 *     .busy      --busy blue    a GET / PUT is in flight
 *     .offline   --warn gold    working offline, changes queued
 *
 * .toast  a bottom-centre transient message. Put <div id="toast" class="toast">
 * in the page and call NayiveUI.toast("Guardado"). For confirmations only, not
 * for errors that need the user to act.
 * ---------------------------------------------------------------------------*/


/* -----------------------------------------------------------------------------
 * PANE RESIZER  (.pane-resizer)
 *
 * A 5px drag handle between two side-by-side panes (Drive tree, Chat list).
 * The app's JS sets a width custom property and hides it on a phone.
 * ---------------------------------------------------------------------------*/
.pane-resizer
{
    flex: 0 0 auto; width: 5px; cursor: col-resize; background: var(--line);
    touch-action: none;
}
.pane-resizer:hover, .pane-resizer.dragging { background: var(--accent); }


/* A long-press on a button would otherwise pop the iOS callout / selection menu.
   Menus too: a long-press opens a menu under the finger and, if the finger stays
   down, iOS goes on and selects the word under it ("Responder" painted blue). */
button,
.top-menu,
.popup,
.menu-item
{
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}
/* A field inside a menu or popup still takes typing and selection (iOS). */
.top-menu :is(input, textarea),
.popup    :is(input, textarea)
{
    -webkit-user-select: text;
    user-select: text;
}


/* -----------------------------------------------------------------------------
 * HELP DIALOG  (.intro-*)
 *
 * Opened by the "?" button at the end of an app's toolbar (a normal toolbar
 * button carrying [data-intro-open]) - never on its own. Built on the shared
 * .sheet by shared/ui.js (NayiveUI.firstRun / .showIntro): app name, an optional
 * one-line summary, then one row per toolbar button (its glyph + name + a short
 * explanation). The launcher's copy adds the "volver a las apps" tip and a
 * "No volver a mostrar" button, and re-opens every visit until that is clicked.
 * Closes on its corner × / Escape only, like any Nayive dialog.
 * ---------------------------------------------------------------------------*/
.intro-btns
{
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.intro-btns li
{
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 8px 2px;
    font-size: 0.86rem;
    line-height: 1.35;
}
.intro-btns li + li  { border-top: 1px solid var(--line); }

.intro-btn-i
{
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
    font-size: 0.8rem;
}
.intro-btn-i svg     { width: 18px; height: 18px; display: block; }

/* A `{ section }` row: a line across, then a small subtitle over the next list. */
.intro-sep           { border: 0; border-top: 1px solid var(--line); margin: 14px 0 0; }
.intro-sub           { margin: 12px 0 0; font-size: 0.95rem; font-weight: 600; color: var(--text); }
.intro-sub + .intro-btns { margin-top: 4px; }

.intro-btn-t         { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.intro-btn-t b       { font-weight: 600; color: var(--text); }
.intro-btn-t span    { color: var(--text-dim); }

/* Close (x), top-right corner like every Nayive dialog. Floated so the title
   flows beside it; sticky so it stays in the corner while a long list scrolls
   under it (hence the --card backdrop). */
.intro-close
{
    position: sticky;
    top: 0;
    float: right;
    margin: -2px -4px 2px 10px;
    background: var(--card);
    border-radius: 50%;
    box-shadow: 0 0 0 5px var(--card);   /* widen the mask over scrolling rows */
    z-index: 2;
}

/* The optional `tip:` line under the list (launcher: "tap the app name / icon
   to go back"; calendar: the daily Tareas sync). */
.intro-home-hint
{
    margin-top: 14px;
    font-size: 0.82rem;
    line-height: 1.4;
    color: var(--text-dim);
}

/* Launcher only: the wide "No volver a mostrar" button, alone in the bottom row. */
.intro-dismiss
{
    flex: 1 1 auto;
    min-height: 44px;
    padding: 8px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    background: var(--card2);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    transition: color var(--tr-fast), background var(--tr-fast);
}
.intro-dismiss:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 12%, var(--card2)); }



/* ---------------------------------------------------------------------------
 * COACH MARKS  (.coach-*)
 *
 * The two bubbles a brand-new user meets ONCE, in the first Nayive app they
 * open (shared/ui.js, NayiveUI.showCoach). A dim overlay over the whole page,
 * an accent ring around each target - the app name/icon and the "?" button -
 * and a small bubble hanging off each ring with a pointer and a hairline that
 * keeps the two joined when the bubble has to move aside on a phone.
 *
 * The corner button counts 9..1 before it turns into a real (x): while it is
 * counting nothing on the page can be tapped, so the two lines get read.
 * Above .sheet (100) and .info-popup (200) - the help sheet must never open
 * underneath the very bubble that is pointing at its button.
 * ---------------------------------------------------------------------------*/
.coach-overlay
{
    position: fixed;
    inset: 0;
    z-index: 300;
    background: var(--scrim);
    opacity: 0;
    transition: opacity var(--tr-fast);
}
.coach-overlay.open { opacity: 1; }

/* Rings + hairlines in the lower layer, bubbles in the upper one. */
.coach-layer { position: absolute; inset: 0; pointer-events: none; }
.coach-layer + .coach-layer > * { pointer-events: auto; }

/* The target stays lit through the scrim; the ring says which one it is. */
.coach-ring
{
    position: fixed;
    border: 2px solid var(--accent);
    border-radius: var(--radius-s);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 28%, transparent);
    pointer-events: none;
}

/* Ring -> bubble hairline (only visible when the bubble sits away from it). */
.coach-line
{
    position: fixed;
    width: 2px;
    background: var(--accent);
    opacity: 0.75;
    pointer-events: none;
}

.coach-bubble
{
    position: fixed;
    max-width: 240px;
    padding: 12px 14px;
    background: var(--card);
    color: var(--text);
    border: 1px solid var(--accent);
    border-radius: var(--radius-m);
    box-shadow: var(--elev-dialog);
}
.coach-bubble p
{
    margin: 0;
    padding-right: 30px;          /* room for the corner countdown / (x) */
    font-size: 0.88rem;
    line-height: 1.4;
}

/* The pointer: a rotated square poking out of the bubble's top edge (bottom
   edge when the bubble had to sit above its target). ui.js sets its left. */
.coach-arrow
{
    position: absolute;
    top: -6px;
    width: 11px;
    height: 11px;
    margin-left: -6px;
    background: var(--card);
    border-left:   1px solid var(--accent);
    border-top:    1px solid var(--accent);
    transform: rotate(45deg);
}
.coach-bubble--up .coach-arrow
{
    top: auto;
    bottom: -6px;
    border: 0;
    border-right:  1px solid var(--accent);
    border-bottom: 1px solid var(--accent);
}

/* Countdown, then the close (x). Same 32px disc as .sheet-close, but pinned in
   the corner and dimmed - and unclickable - while it is still a number. */
.coach-close
{
    position: absolute;
    top: 6px;
    right: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    cursor: default;
}
.coach-close svg          { width: 17px; height: 17px; display: block; }
.coach-close:not(:disabled)
{
    color: var(--text);
    cursor: pointer;
}
.coach-close:not(:disabled):hover
{
    background: color-mix(in srgb, var(--text) 9%, transparent);
}

@media (max-width: 640px)
{
    .coach-bubble   { max-width: 232px; }
    .coach-bubble p { font-size: 0.85rem; }
}

/* ---------------------------------------------------------------------------
 * GUIDED TOUR  (.tour-*)
 *
 * The help card's "Ver el tour" (shared/ui.js, runTour): one lit button at a
 * time. Four dim panes frame a hole over the target; a clear lid covers the
 * hole so the page cannot be touched, except on a "tap" step, where the lid
 * goes and the user's tap reaches the real button. Ring, bubble, pointer and
 * (x) are the coach marks' own (.coach-*). A dashed ring is a "ghost": the
 * place where the button shows up once there is something to act on.
 * ---------------------------------------------------------------------------*/
.tour-overlay           { position: fixed; inset: 0; z-index: 2000; pointer-events: none; }   /* over every menu, popup and sheet: Calc lifts those to 400-700 */
.tour-overlay > *       { pointer-events: auto; }
.tour-pane              { position: fixed; background: var(--scrim); }
.tour-lid               { position: fixed; }
.tour-overlay.is-tap .tour-lid { display: none; }
.tour-ring              { pointer-events: none; transition: left var(--tr-fast), top var(--tr-fast), width var(--tr-fast), height var(--tr-fast); }
.tour-ring.is-ghost     { border-style: dashed; }
.tour-bubble            { max-width: 300px; }
.tour-head              { display: flex; align-items: center; gap: 8px; margin: 0 30px 6px 0; }
.tour-head .intro-btn-i { flex: 0 0 auto; }
.tour-note              { margin-top: 6px !important; color: var(--text-dim); font-style: italic; }
.tour-row               { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.tour-count             { flex: 1; color: var(--text-dim); font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.intro-tour             { margin: 4px 0 10px; }

/* ---------------------------------------------------------------------------
 * INSTALL-AS-APP DIALOG  (.install-*)
 *
 * Built by NayiveUI.offerInstall() on the shared .sheet, reusing the help
 * dialog's row list (.intro-btns) for the three "what you get" lines and its
 * wide button (.intro-dismiss) for the actions. Only the app logo, the numbered
 * iOS steps and the filled primary button are new.
 * ---------------------------------------------------------------------------*/
/* The corner x is pinned instead of floated here: floating it steals width from
   the first line and pushes the centred logo off-centre. */
.install-sheet .intro-close
{
    position: absolute;
    top: 12px;
    right: 12px;
    float: none;
    margin: 0;
    box-shadow: none;
}
.install-logo
{
    display: block;
    width: 64px;
    height: 64px;
    margin: 2px auto 10px;
    border-radius: var(--radius-l);
    box-shadow: var(--elev-card);
}
.install-title { text-align: center; }
.install-lead  { text-align: center; }

.install-h     { margin-top: 16px; }

/* The three iOS steps: a counter bubble, the real glyph, then the sentence. */
.install-steps
{
    counter-reset: install-step;
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
}
.install-steps li
{
    display: flex;
    gap: 10px;
    align-items: center;
    padding: 8px 2px;
    font-size: 0.86rem;
    line-height: 1.35;
}
.install-steps li + li { border-top: 1px solid var(--line); }
.install-steps li::before
{
    counter-increment: install-step;
    content: counter(install-step);
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--card2);
    color: var(--text-dim);
    font-size: 0.78rem;
    font-weight: 600;
}
.install-steps b { font-weight: 600; color: var(--text); }

.install-note
{
    margin-top: 14px;
    font-size: 0.85rem;
    line-height: 1.4;
    color: var(--text-dim);
}

/* Stacked full-width actions: Instalar / Ahora no / No mostrar más. */
.install-actions
{
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 16px;
}
.intro-dismiss.primary
{
    border-color: transparent;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 0.95rem;
    font-weight: 600;
}
.intro-dismiss.primary:hover { background: var(--accent); color: var(--on-accent); filter: brightness(1.06); }
.install-never { border-color: transparent; background: none; font-size: 0.8rem; min-height: 36px; }
.install-never:hover { background: none; color: var(--text); text-decoration: underline; }

/* ---------------------------------------------------------------------------
   SHARING  -  the "Compartir" dialog (NayiveUI.shareSheet) and the read-only
   badge a shared item shows in an app header.
   ------------------------------------------------------------------------- */
.ro-badge
{
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    background: var(--card);
    font-size: 0.78rem;
    color: var(--text-dim);
    white-space: nowrap;
}

/* The padlock is BLUE on purpose: it is the one thing in the chip that has to
   read at a glance ("you cannot change this"), and blue is the only tone the
   apps never use for an action. --link is the blue token, deep enough on the
   beige light ground and bright enough on the dark one. */
.ro-badge svg { flex: 0 0 auto; width: 15px; height: 15px; color: var(--link); }

/* Who already has it: one row per person, with a round × to take it back. */
.share-have
{
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0;
}
.share-row
{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 6px 6px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--card);
}
.share-drop
{
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-dim);
    cursor: pointer;
}
.share-drop:hover  { background: color-mix(in srgb, var(--text) 9%, transparent); color: var(--text); }
.share-drop svg    { width: 16px; height: 16px; }
.share-drop[disabled] { opacity: 0.5; cursor: default; }

/* Who else could have it: one list box, the names in three columns, a tick-box
 * in front of each. A name too wide for its column ends in "..." (full name in
 * the tooltip). The tick-box is the browser's own <input type="checkbox">
 * (coloured by the accent-color rule up in the INPUT section), so it draws the
 * same everywhere. */
.share-pick
{
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-content: start;
    gap: 2px;
    max-height: 44vh;
    overflow-y: auto;
    margin: 12px 0 4px;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--card);
}
.share-user
{
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 8px;
    border: 1px solid transparent;
    border-radius: var(--radius-s);
    cursor: pointer;
}
.share-user span  { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.share-user:hover { background: color-mix(in srgb, var(--text) 7%, transparent); }

/* "Pueden añadir archivos" - one tick under the people list. Deliberately not
   inside the .share-pick box: it is a property of the share, not of a person. */
.share-add
{
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 10px 2px 0;
    font-size: 0.86rem;
    color: var(--text-dim);
    cursor: pointer;
}
.share-add input { flex: 0 0 auto; width: auto; margin: 0; }
.share-add > .switch { margin-left: auto; }   /* an on / off row: the text, then the switch */
.share-user.on    { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.share-user input { flex: 0 0 auto; width: auto; margin: 0; }

/* "Todos" - the first row of the list, the whole width. While it is ticked the
   names are greyed out: everybody is in already. */
.share-all { grid-column: 1 / -1; }
.share-pick.all .share-user:not(.share-all) { opacity: 0.45; cursor: default; }
.share-pick.all .share-user:not(.share-all):hover { background: none; }

/* The sheet's sections: "Cualquiera con el enlace" (a trip only, on top) and
   the people. Each has a heading and a note; the divider goes only BETWEEN two
   of them, so a sheet with just the people (Drive, Photos, Split) gets none. */
.share-sect + .share-sect { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--line); }
.share-head { margin: 0 0 4px; font-size: 0.9rem; font-weight: 600; color: var(--text); }
.share-note { margin: 0 0 10px; font-size: 0.8rem; line-height: 1.4; color: var(--text-dim); }

/* A trip's public link: a plain .share-row with .share-drop buttons; only the
   long URL has to give way. The row keeps its height while the link is still
   being made and has no buttons yet. */
.share-link-url  { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.86rem; }
.share-link .share-row { gap: 2px; min-height: 46px; }
.share-link-app  { margin-top: 12px; }
.share-link-how  { margin: 6px 0 0; }
/* "My location": one card per app (Overland on iPhone, GPSLogger on Android),
   each with its own address and links. A PC shows both. */
.loc-app + .loc-app { margin-top: 16px; }
.loc-links { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 0; }
.loc-link  { display: inline-flex; align-items: center; background: var(--card2); border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 6px 12px; font-size: 0.8rem; color: var(--link); text-decoration: none; }
.loc-link:hover { border-color: color-mix(in srgb, var(--text) 30%, transparent); }
/* Location sending not set up: the (i) sits right after "not set up", the "+" stays right. */
.share-link-app .info-dot                { margin-right: auto; }


/* -----------------------------------------------------------------------------
 * EMPTY STATE  (.empty-state)
 *
 * What a list shows when it has nothing to list: the app's own glyph, faint, a
 * bold one-line title and a quieter second line. Same block in Tasks, Habits,
 * Split, Contacts. Toggle it with the `hidden` attribute.
 *
 *     <div class="empty-state" hidden>
 *       <svg ...></svg>
 *       <p class="empty-title">Sin tareas</p>
 *       <p>Pulsa + para crear la primera.</p>
 *     </div>
 * ---------------------------------------------------------------------------*/
.empty-state
{
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 36px 10px 18px;
    color: var(--text-faint);
}
.empty-state svg         { width: 44px; height: 44px; margin-bottom: 10px; opacity: 0.6; }
.empty-state p           { margin: 4px 0 0; font-size: 0.8rem; }
.empty-state .empty-title,
.empty-state p:first-of-type { margin: 0; font-size: 0.93rem; font-weight: 600; color: var(--text-dim); }
.empty-state .text-btn   { margin-top: 14px; }


/* -----------------------------------------------------------------------------
 * IDENTITY COLOURS  ([data-c="0"] ... [data-c="7"]  ->  var(--id-color))
 *
 * The theme has one accent; people (Split) and habits (Habits) have to be told
 * apart at a glance, so each gets one of eight fixed hues by its index. They
 * are identity colours, not theme colours: the same in light and dark, and
 * white text reads on all of them. The page paints with var(--id-color).
 * ---------------------------------------------------------------------------*/
[data-c="0"] { --id-color: hsl(170 40% 38%); }
[data-c="1"] { --id-color: hsl(215 45% 50%); }
[data-c="2"] { --id-color: hsl(345 45% 52%); }
[data-c="3"] { --id-color: hsl(35 55% 46%); }
[data-c="4"] { --id-color: hsl(275 35% 52%); }
[data-c="5"] { --id-color: hsl(95 35% 40%); }
[data-c="6"] { --id-color: hsl(15 50% 52%); }
[data-c="7"] { --id-color: hsl(200 25% 45%); }


/* -----------------------------------------------------------------------------
 * TOOLBAR GROUP POPUP  (.has-popup button  ->  .popup > .popup-row > .pop-btn)
 *   -  NayiveOffice.groupPopup
 *
 * A run of near-identical toolbar buttons - the six alignments, the four
 * row/column edits, the two sorts - costs six slots on the bar and reads as
 * noise. One button takes their place and the REAL buttons live in a small
 * card under it: the same buttons, the same handlers, the same "active"
 * light, just somewhere else. Calc's border picker has worked this way from
 * the start; this only gives the shape a name so Write and the rest can use
 * it too.
 *
 * position: fixed, anchored under its trigger in JS, like .top-menu: the
 * toolbars carry overflow and would clip an absolute child.
 *
 * `.has-popup` is the mark ON THE TRIGGER: a hairline caret in the corner,
 * so a button that opens something never looks like a button that does
 * something. It goes on the app's own toolbar button class (.fmt-btn,
 * .icon-btn, .tb-btn), never instead of it.
 * -------------------------------------------------------------------------- */
.popup
{
    position: fixed;
    z-index: 200;
    display: none;
    padding: 6px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    box-shadow: var(--elev-dialog);
}
.popup.open              { display: block; }
.popup-row               { display: flex; gap: 2px; }
.popup-row + .popup-row  { margin-top: 2px; }
.popup-row-sep           { margin-top: 5px; padding-top: 6px; border-top: 1px solid var(--line); }

.pop-btn
{
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
    padding: 0;
    border: none;
    border-radius: 50%;
    color: var(--text-dim);
    font: inherit;
    cursor: pointer;
}
.pop-btn.active,
.pop-btn.is-active       { background: color-mix(in srgb, var(--accent) 24%, transparent); color: var(--text); }
.pop-btn:disabled        { opacity: 0.45; cursor: default; background: transparent; color: var(--text-dim); }

.has-popup               { position: relative; }
.has-popup::after
{
    content: "";
    position: absolute;
    right: 2px;
    bottom: 2px;
    border-left: 3px solid transparent;
    border-right: 3px solid transparent;
    border-top: 3px solid currentColor;
    opacity: 0.75;
}


/* -----------------------------------------------------------------------------
 * "MORE" MENU  (.top-menu > .menu-item)  -  NayiveUI.wireMenu
 *
 * The small popup a header "⋮" button opens: view switch (Calendar), the
 * phone-only import / export (Contacts), the file buttons (Calc, Write).
 * position: fixed and anchored under its button by shared/ui.js, so a .topbar
 * with overflow never clips it. One action per row: an 18px glyph + a label.
 * `.mi-check` marks the current choice in a radio-style menu; `.menu-sep` is a
 * hairline between groups, `.menu-label` a small heading over one; `.danger` is a destructive row.
 * ---------------------------------------------------------------------------*/
.top-menu
{
    position: fixed;
    z-index: 150;
    min-width: 210px;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    box-shadow: var(--elev-dialog);
}
.menu-item
{
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 11px 12px;
    border: none;
    border-radius: var(--radius-s);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
    text-align: left;
    cursor: pointer;
}
.menu-item svg           { width: 18px; height: 18px; flex: 0 0 auto; color: var(--text-dim); }
.menu-item:hover         { background: color-mix(in srgb, var(--text) 9%, transparent); }
.menu-item:active        { background: color-mix(in srgb, var(--text) 14%, transparent); }
.menu-item .mi-check     { width: 15px; flex: 0 0 auto; color: var(--accent); visibility: hidden; }
.menu-item.is-active .mi-check { visibility: visible; }
.menu-item.danger,
.menu-item.danger svg    { color: var(--danger); }
.menu-sep                { height: 1px; margin: 4px 6px; background: var(--line); }
.menu-label              { padding: 6px 12px 2px; font-size: 0.75rem; font-weight: 600; color: var(--text-dim); }

/* A row for a button that IS on the bar on a PC and only disappears on a
   phone (Write's print, page setup, comments...). The menu carries it for
   the phone and drops it here, so a PC never offers the same thing twice. */
@media (min-width: 641px)
{
    .top-menu .mi-phone  { display: none; }
}

/* -----------------------------------------------------------------------------
 * PULL-DOWN MENU BAR  (#menuBar > .menu-top, .menu-panel)  -  shared/menubar.js
 *
 * The classic "Archivo · Edición · Ver …" row Write and Calc offer instead of
 * their icon toolbar. The bar is one row of text buttons; the drop-downs reuse
 * the .top-menu / .menu-item vocabulary above (card, border, shadow, tick), and
 * only tighten the rows and add the two things a pull-down has that a "⋮" popup
 * does not: a shortcut column on the right and a submenu arrow.
 *
 * Each app keeps its own `#app.is-menus …` rule for collapsing its toolbar -
 * what has to disappear differs, and Write's strip must collapse with
 * visibility:hidden rather than display:none (its buttons stay in the DOM,
 * measured, for the menu entries that click them).
 * ---------------------------------------------------------------------------*/
#menuBar
{
    flex: 0 0 auto;
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 1px; padding: 2px 6px;
    border-bottom: 1px solid var(--line); background: var(--card);
}

.menu-top
{
    padding: 5px 10px;
    border: none; border-radius: var(--radius-s);
    background: none; color: var(--text);
    font: inherit; font-size: 0.92rem; line-height: 1.2;
    white-space: nowrap; cursor: pointer;
}
.menu-top:hover                 { background: color-mix(in srgb, var(--text)  9%, transparent); }
.menu-top[aria-expanded="true"] { background: color-mix(in srgb, var(--text) 16%, transparent); }

.menu-panel                      { max-height: calc(100vh - 80px); overflow-y: auto; min-width: 236px; }
.menu-panel .menu-item           { padding: 7px 10px; font-size: 0.9rem; gap: 8px; }
.menu-panel .menu-item[disabled] { opacity: 0.42; cursor: default; }
.menu-panel .menu-item[disabled]:hover { background: none; }
.menu-panel .mi-label            { flex: 1 1 auto; }
.menu-panel .mi-key              { flex: 0 0 auto; padding-left: 20px; color: var(--text-dim); font-size: 0.8rem; }
.menu-panel .mi-arrow            { flex: 0 0 auto; padding-left: 20px; color: var(--text-dim); font-size: 0.8rem; }

/* A colour entry (text colour / fill) shows the colour itself. */
.menu-panel .mi-swatch
{ width: 15px; height: 15px; flex: 0 0 auto; border: 1px solid var(--line); border-radius: 3px; }

/* The glyph before the label - the toolbar button's own icon, cloned (see
   `iconOf` / `icon` in shared/menubar.js). A row with none keeps the empty
   slot, so the labels of one panel still line up. A glyph that brings no fill
   of its own (an icon-font path) takes the row's colour, as in its toolbar. */
.menu-panel .mi-icon
{
    width: 18px; height: 18px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-dim);
}
.menu-panel .mi-icon svg              { width: 16px; height: 16px; }
.menu-panel .mi-icon svg:not([fill])  { fill: currentColor; }


/* -----------------------------------------------------------------------------
 * DATE / TIME FIELD  (.dt-field)
 *
 * A native <input type=date|time> shows its value in the browser's own locale
 * (mm/dd/yyyy). The Nayive rule is yyyy-mm-dd and 24 h, so the native text is
 * hidden (color: transparent) and a .dt-display overlay shows the ISO value;
 * the input stays underneath, so a tap still opens the native picker. The app
 * keeps the overlay's text in step on `input` (Calendar, Contacts, Split, Trips).
 * `.placeholder` on the overlay draws it dim while the field is empty.
 *
 *     <div class="dt-field"><input type="date" id="x"><span class="dt-display"></span></div>
 * ---------------------------------------------------------------------------*/
.dt-field                { position: relative; }
.dt-field input[type=date],
.dt-field input[type=time] { color: transparent; }
.dt-field .dt-display
{
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    padding: 0 10px;
    font-size: var(--field-fs);
    color: var(--text);
    pointer-events: none;
    white-space: nowrap;
}
.dt-field .dt-display.placeholder { color: var(--text-dim); }


/* -----------------------------------------------------------------------------
 * "MORE FIELDS" TOGGLE  (.more-toggle)
 *
 * The pill-on-a-line that folds the rarely used half of a long form away
 * (Calendar's event sheet, Contacts' edit form). The app toggles .open and
 * flips the label; the chevron turns on its own.
 * ---------------------------------------------------------------------------*/
.more-toggle
{
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin: 4px 0 14px;
    padding: 6px 0;
    background: none;
    border: none;
    color: var(--text-dim);
    font-family: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}
.more-toggle .more-pill
{
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    padding: 6px 14px;
    background: var(--card2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    transition: background-color var(--tr-fast), border-color var(--tr-fast);
}
.more-toggle:hover .more-pill  { background: color-mix(in srgb, var(--text) 10%, var(--card2)); border-color: color-mix(in srgb, var(--text) 22%, transparent); }
.more-toggle:active .more-pill { background: color-mix(in srgb, var(--text) 16%, var(--card2)); }
.more-toggle .more-chevron     { display: inline-block; font-size: 1rem; line-height: 1; transition: transform var(--tr-move); }
.more-toggle.open .more-chevron { transform: rotate(90deg); }
.more-toggle .more-line        { flex: 1 1 auto; height: 1px; background: var(--line); }


/* -----------------------------------------------------------------------------
 * OFFICE CHROME  (.file-label, .open-list, .folder-pick, .sc-list, .st-list)  -  Calc, Text, Write
 *
 * The three document editors share one header shape: after the app name, the
 * open document's name (.file-label - click it to rename in place, which swaps
 * in .file-label-input), then the sync plug. Their "Abrir documento" dialog is
 * one plain folder browser: a breadcrumb (.open-crumb) over a one-level list
 * (.open-list) of folders and openable files. Their "Guardar como" dialog asks
 * where the document goes with .folder-pick - a button dressed as a field
 * control that opens NayiveUI.pickFolder. shared/office.js builds all three.
 * ---------------------------------------------------------------------------*/
/* The document name is an EDIT IN PLACE pair: .file-label (span, at rest) swaps
 * for .file-label-input (the box). Both carry the shared .field-edit-text /
 * .field-edit look - only the header layout is set here. A name too long to fit
 * loses its START ("...informe 2026.docx"): the end, with the extension, is what
 * tells documents apart. rtl puts the ellipsis on the left; the name itself sits
 * in a <bdi> so it still reads left to right. */
.file-label
{
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    direction: rtl;
    text-align: left;
}
.file-label-input        { flex: 1 1 auto; min-width: 0; }

/* The two phone toggles of Calc and Write (each app shows them in its own
 * phone block): the header's "Aa" (#fmtBtn) folds the toolbar away while you
 * type - a TEXT button, not an icon one: "Aa" says "formato" at a glance - and
 * the toolbar's own "⋮" (#moreToolsBtn) opens the rest of it in place; open,
 * it takes the tint of a property that is on. Write's is also matched inside
 * #toolbarRow, to outweigh that row's own button rule. */
#fmtBtn
{
    font-family: var(--font-sans);
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.5px;
}
#moreToolsBtn[aria-expanded="true"],
#toolbarRow #moreToolsBtn[aria-expanded="true"] { background: color-mix(in srgb, var(--accent) 24%, transparent); color: var(--text); }

/* The save floppy before the sync plug (NayiveOffice.autosave): a readout, not
 * a control. Plain text colour while edits wait, blue saving, green saved;
 * "here" = green with a blue dot: saved on this device, not sent yet
 * (store.js PARKED SAVES); amber "draft" = on this device only; red only when
 * something went wrong. Its title says "Guardado 12:04" / "Borrador 12:04".
 * Empty (no state) until a document is open. */
.saved-at                { display: inline-flex; align-items: center; margin-left: 6px; color: var(--danger); position: relative; }
.saved-at:not([data-state]) { display: none; }
.saved-at svg            { width: 18px; height: 18px; }
.saved-at[data-state="pending"] { color: var(--text); }
.saved-at[data-state="draft"]   { color: var(--warn); }
.saved-at[data-state="saving"] { color: var(--busy); }
.saved-at[data-state="saved"],
.saved-at[data-state="here"]   { color: var(--ok); }
.saved-at[data-state="here"]::after { content: ""; position: absolute; top: -1px; right: -3px; width: 7px; height: 7px;
                                      border-radius: 50%; background: var(--busy); }

.open-crumb              { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin-bottom: 8px;
                           font-size: 0.85rem; color: var(--text-dim); }
.open-crumb.is-note      { display: block; font-style: italic; }
.open-crumb .crumb-seg   { background: none; border: none; padding: 3px 5px; border-radius: var(--radius-s);
                           color: var(--accent); font: inherit; cursor: pointer; }
.open-crumb .crumb-seg:disabled { color: var(--text-dim); cursor: default; }
.open-crumb .crumb-seg:not(:disabled):hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.open-crumb .crumb-sep   { color: var(--text-dim); }

.open-list               { list-style: none; margin: 0 0 14px; padding: 0; min-height: 120px; max-height: 46vh;
                           overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-s); }
.open-list li            { display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer;
                           border-bottom: 1px solid var(--line); font-size: 0.92rem; }
.open-list li:last-child { border-bottom: none; }
.open-list li:hover      { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.open-list li.is-loading,
.open-list li.is-empty   { cursor: default; color: var(--text-dim); }
.open-list li.is-loading:hover,
.open-list li.is-empty:hover { background: none; }
.open-list li.is-empty   { justify-content: center; padding: 16px; font-size: 0.88rem; }
.open-list .open-ic      { flex: 0 0 auto; display: flex; color: var(--text-dim); }
.open-list .open-ic svg  { width: 17px; height: 17px; }
.open-list .open-nm      { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.open-list li.is-folder .open-nm { font-weight: 600; }
.open-list li.is-folder::after   { content: "\203A"; color: var(--text-dim); flex: 0 0 auto; padding-right: 2px; }

/* The folder row of a save-as dialog. It sits inside a .field, so it has to
 * look exactly like the <input> above it - only the folder icon and the
 * pointer say it opens something. */
.folder-pick
{
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    font: inherit;
    font-size: var(--field-fs);
    text-align: left;
    color: var(--text);
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    cursor: pointer;
}
.folder-pick:hover,
.folder-pick:focus-visible { border-color: color-mix(in srgb, var(--text) 30%, transparent); outline: none; }
.folder-pick .fp-btn-ic     { flex: 0 0 auto; display: flex; color: var(--text-dim); }
.folder-pick .fp-btn-ic svg { width: 16px; height: 16px; }
.folder-pick .fp-btn-nm     { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* NayiveUI.pickFolder / pickFile: the folder tree in the sheet (.fp-tree, one
   .fp-row per folder), and for pickFile the crumb of the folder on show and
   a file's size. */
.fp-tree  { margin: 4px 0 2px; min-height: 240px; max-height: min(66vh, 560px); overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-m); }
.fp-msg   { margin: 0; padding: 16px; color: var(--text-dim); font-size: .9rem; line-height: 1.4; }
.fp-row
{
    display: flex; align-items: center; gap: 8px; padding: 9px 10px 9px 8px; cursor: pointer;
    border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.fp-row:last-child { border-bottom: none; }
.fp-row:hover      { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.fp-row.sel        { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.fp-row.sel .fp-ic,
.fp-row.sel .fp-name { color: var(--text); }
.fp-caret
{
    flex: 0 0 auto; width: 20px; align-self: stretch; display: flex; align-items: center;
    justify-content: center; color: var(--text-dim); font-size: .8rem;
}
.fp-caret.has:hover { color: var(--text); }
.fp-ic     { flex: 0 0 auto; display: flex; color: var(--text-dim); }
.fp-ic svg { width: 16px; height: 16px; }
.fp-name   { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .9rem; }
.fp-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin: 0 0 6px; font-size: .85rem; color: var(--text-dim); }
.fp-crumbs button { border: none; background: none; padding: 2px 4px; border-radius: var(--radius-s); font: inherit; color: var(--link); cursor: pointer; }
.fp-crumbs button:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.fp-crumbs b { padding: 2px 4px; color: var(--text); font-weight: 600; }
.fp-size   { flex: 0 0 auto; font-size: .78rem; color: var(--text-dim); }

/* Help ▸ "Keyboard shortcuts" (Calc, Write): one row per shortcut, its keys on
   the right. NayiveOffice.showShortcuts fills #scList. */
.sc-list { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 10px; }
.sc-row  { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; padding: 5px 0; }
.sc-row + .sc-row { border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent); }
.sc-row kbd
{
    font-family: var(--font-mono); font-size: 0.78rem; white-space: nowrap;
    color: var(--text-dim); border: 1px solid var(--line);
    border-radius: var(--radius-s); padding: 1px 6px; background: var(--card2);
}

/* Help ▸ "Statistics" (Calc, Text, Write): label left, number right, the same
   rhythm as the shortcut sheet above. NayiveOffice.showStats fills #stList. */
.st-list { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 6px; }
.st-row  { display: flex; align-items: baseline; justify-content: space-between; gap: 32px; padding: 5px 0; }
.st-row + .st-row { border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent); }
.st-row b { font-variant-numeric: tabular-nums; }
.st-note { font-size: 0.78rem; color: var(--text-dim); margin: 0 0 4px; }


/* -----------------------------------------------------------------------------
 * MEDIA VIEWER  (.main-row, .view-head, .media-card, .scope-chip, .empty-hint, .player-bar)
 *
 * Photos, Music and Movies are one app shape wearing three skins: a folder tree
 * (or scope chip) on the left, a scrolling library in the middle, a transport
 * bar at the bottom. They already share their JavaScript (shared/media.js);
 * these are the rules all three (or, for the player bar, the two with a
 * transport) had pasted into their own <style> byte for byte.
 *
 * What stays per-app is only the sizes that genuinely differ: the poster is
 * 34x50, the album cover 48x48; Movies' elapsed label holds "1:23:45" and needs
 * 54px where Music's "3:21" needs 34. Those overrides sit in the app, each next
 * to the grid it belongs to.
 * ---------------------------------------------------------------------------*/
.main-row          { flex: 1 1 auto; display: flex; min-height: 0; }

/* A library view's heading (Music, Movies). Its bottom margin is the app's. */
.view-head         { font-size: 0.95rem; font-weight: 700; color: var(--text); overflow-wrap: anywhere; }
.view-head small   { margin-left: 8px; font-size: 0.78rem; font-weight: 400; color: var(--text-dim); }

/* A card in a library grid (Music's albums and artists, Movies' genres): its
   title and the line under it. The grid, the art and any other padding are
   the app's. */
.media-card
{
    display: flex; flex-direction: column; gap: 10px; padding: 12px;
    background: var(--card2); border: 1px solid var(--line); border-radius: var(--radius-m);
    cursor: pointer; text-align: left; font: inherit; color: inherit;
    transition: border-color var(--tr-fast), background-color var(--tr-fast);
}
.media-card:hover     { border-color: color-mix(in srgb, var(--text) 22%, transparent); }
.media-card.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--card2)); }
.media-card-title     { font-size: 0.86rem; font-weight: 600; color: var(--text);
                        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-card-sub       { font-size: 0.76rem; color: var(--text-dim); }

/* "You are looking at <folder> [x]" - the pill above a scoped library. */
.scope-chip
{
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px 6px 6px 12px;
    background: var(--card2);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    color: var(--text-dim);
    font-size: 0.78rem;
}
.scope-chip button
{
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--text-dim);
    cursor: pointer;
    transition: background-color var(--tr-fast), color var(--tr-fast);
}
.scope-chip button:hover    { background: color-mix(in srgb, var(--text) 14%, transparent); color: var(--text); }
.scope-chip button svg      { width: 12px; height: 12px; }

/* The media apps' "nothing to show yet" panel. (.empty-state above is the
   list apps' version; this one is centred in a whole pane and can carry a
   .text-btn call to action.) */
.empty-hint
{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 44px 20px;
    color: var(--text-faint);
    text-align: center;
}
.empty-hint svg             { width: 32px; height: 32px; opacity: 0.5; }
.empty-hint p               { margin: 0; font-size: 0.86rem; max-width: 320px; }
.empty-hint .text-btn       { margin-top: 6px; }

/* The transport bar pinned under the library (Music, Movies). Left: what is
   playing. Middle: the buttons and the seek bar. Right: volume. */
.player-bar
{
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 12px 22px;
    border-top: 1px solid var(--line);
    background: var(--card);
}
/* Left third of the bar: the cover, then the title over the sub-line. Only the
   COVER's box differs between the two apps (a 34x50 poster, a 48x48 sleeve) and
   with it .player-now's width - both stay in the app. */
.player-now                 { display: flex; align-items: center; gap: var(--space-3); flex: 0 0 auto; min-width: 0; }
.player-art                 { display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
                              color: rgba(255, 255, 255, 0.85); }
.player-meta                { min-width: 0; }
.player-title               { font-size: 0.88rem; font-weight: 600; color: var(--text);
                              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.player-sub                 { font-size: 0.76rem; color: var(--text-dim);
                              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.transport                  { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.transport-btn
{
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 50%;
    color: var(--text-dim);
    cursor: pointer;
}
.transport-btn:disabled       { opacity: 0.4; cursor: default; }
.transport-btn:disabled:hover { background: transparent; color: var(--text-dim); }
.transport-btn svg            { width: 18px; height: 18px; }

/* The one accent-filled control in the bar: play / pause. */
.play-btn                   { width: 44px; height: 44px; background: var(--accent); color: var(--on-accent); }
.play-btn:hover             { background: var(--accent); color: var(--on-accent); filter: brightness(1.08); }
.play-btn svg               { width: 20px; height: 20px; }

.seek-block                 { flex: 1 1 auto; display: flex; align-items: center; gap: var(--space-2); min-width: 120px; }
.time-label                 { flex: 0 0 auto; width: 34px; font-size: 0.72rem; color: var(--text-dim); font-family: var(--font-mono); }
.time-label.end             { text-align: right; }
.volume-block               { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; width: 130px; }
.volume-block svg           { width: 17px; height: 17px; flex: 0 0 auto; color: var(--text-dim); }

/* The seek + volume sliders. The browser's own range input, tokenised. */
input[type=range]
{
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 4px;
    border-radius: 2px;
    background: var(--track);
    outline: none;
}
input[type=range]::-webkit-slider-thumb
{
    -webkit-appearance: none;
    width: 12px; height: 12px; margin-top: -4px;
    border-radius: 50%;
    background: var(--accent);
    cursor: pointer;
}
input[type=range]::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--track); }
input[type=range]::-moz-range-thumb  { width: 12px; height: 12px; border: none; border-radius: 50%;
                                       background: var(--accent); cursor: pointer; }
input[type=range]::-moz-range-track  { height: 4px; border-radius: 2px; background: var(--track); }
input[type=range]:disabled           { opacity: 0.5; }

/* On a narrow window the bar goes to two rows with the seek bar full width
   underneath. That lives in each media app's own narrow block, not here: the two
   drop their side pane at different widths (Music 760, Movies 820) and the bar
   has to fold at the same moment - see TWO LAYOUT MODES at the top of this file.
   */


/* -----------------------------------------------------------------------------
 * IMAGE EDITOR CHROME  (.editor-backdrop > .editor-bar / .editor-canvas /
 *                        .editor-comment)  -  Image (which Drive and Photos open) and Chat
 *
 * The frame around the photo editor (shared/image-editor.js), which the apps
 * load on first use.
 * It is a FIXED DARK surface in both schemes - literal colours on purpose, never
 * the theme tokens: you judge a photo against a neutral dark ground, the same
 * reason Drive's lightbox is always dark. That is the one place in Nayive where
 * a hex colour is the right answer.
 *
 * Chat opens it over its own screens, so it sets a `z-index` on .editor-backdrop
 * (170). Image uses only .editor-canvas + .editor-comment, under its own header.
 * ---------------------------------------------------------------------------*/
.editor-backdrop
{
    position: fixed;
    inset: 0;
    display: none;
    flex-direction: column;
    background: #1e1e1e;
}
.editor-backdrop.open { display: flex; }

.editor-bar
{
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    height: 44px;
    padding: 0 8px 0 14px;
    background: #151515;
    border-bottom: 1px solid #000;
}
.editor-title
{
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.9);
}
.editor-actions { flex: 0 0 auto; display: flex; gap: 6px; }
.editor-btn
{
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.22);
    color: #fff;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}
.editor-btn:hover  { background: rgba(255, 255, 255, 0.34); }
.editor-btn:disabled { opacity: 0.4; cursor: default; background: rgba(255, 255, 255, 0.22); }
.editor-btn svg    { width: 18px; height: 18px; }
/* The same button laid ON a photo or a playing video (a lightbox's ×, a
   slideshow's exit, the film's fullscreen / collapse). A white wash vanishes
   on a bright frame, so this one is a dark glass disc instead. */
.editor-btn.over-media        { background: rgba(0, 0, 0, 0.42); }
.editor-btn.over-media:hover  { background: rgba(0, 0, 0, 0.6); }
.editor-canvas     { flex: 1 1 auto; min-height: 0; position: relative; }

/* A free-text note for the picture, saved into the comments sidecar together
   with the image. No label - the placeholder explains it. */
.editor-comment
{
    flex: 0 0 auto;
    resize: none;
    border: none;
    border-top: 1px solid #000;
    outline: none;
    padding: 8px 14px;
    font: inherit;
    font-size: var(--field-fs);
    line-height: 1.4;
    color: #fff;
    background: #151515;
}
.editor-comment::placeholder { color: rgba(255, 255, 255, 0.4); }

/* The editor itself (shared/image-editor.js) fills the .editor-canvas: the
   tools (.ie-tools, only when the app's header keeps no place for them - Chat),
   a tool's options (.ie-opts) and, under them, the picture (.ie-main, with the
   zoom box over its corner). It is a dark island in both schemes:
   it sets the colour tokens the shared controls read (.icon-btn, .pill, the
   range), so they are the standard ones, on dark. */
.ie
{
    color-scheme: dark;
    --bg:         #151515;
    --card2:      #2A2B2F;
    --line:       #45464C;
    --text:       #F2F2F3;
    --text-dim:   #C9CBD1;
    --text-faint: #8A8D96;
    --accent:     #1F80B1;
    --on-accent:  #FFFFFF;
    --track:      #4A4B50;

    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    color: var(--text);
}
.ie-main  { position: relative; flex: 1 1 auto; min-height: 0; }
/* The scroll bars move the picture; the drawing (.ie-stage) stays put over
   the visible part and Fabric draws what is under it. */
.ie-view
{
    position: absolute;
    inset: 0;
    overflow: auto;
    touch-action: none;
    overscroll-behavior: contain;
}
.ie-view.is-hand     { cursor: grab; }
.ie-view.is-panning  { cursor: grabbing; }
.ie-sizer            { position: relative; }
.ie-stage            { position: sticky; left: 0; top: 0; overflow: hidden; }

/* Hand · zoom out · % · zoom in · fit · 1:1 - over the picture's corner. */
.ie-zoom
{
    position: absolute;
    right: 22px;
    bottom: 22px;
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.62);
}
.ie-zoom-pct { min-width: 46px; text-align: center; font-size: 0.78rem; font-variant-numeric: tabular-nums; color: var(--text); }
.ie-one span { font-size: 0.72rem; font-weight: 700; letter-spacing: -0.02em; }

/* Its bars sit at the top, as every toolbar; groups of .tb-group, the line
   between them the header's own (see HEADER). */
.ie-bar
{
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: safe center;
    gap: 2px;
    padding: 6px 10px;
    overflow-x: auto;
    border-bottom: 1px solid #000;
}
.ie-bar[hidden]      { display: none; }
.ie-tools            { background: var(--bg); }
.ie-opts             { min-height: 50px; background: #1A1A1A; }
/* Adjust's slider drops under its pills when the row is short; Crop's ×
   and ✓ never do - its pills scroll sideways instead. */
.ie-opts:has(> .ie-range) { flex-wrap: wrap; row-gap: 6px; }
.ie-pills            { flex: 0 1 auto; gap: 6px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.ie-pills .pill      { flex: 0 0 auto; }
@media (max-width: 640px)
{
    .ie-pills .pill  { padding-inline: 10px; }
}
.ie-range            { display: flex; align-items: center; gap: 10px; width: min(320px, 70vw); margin-left: 10px; }
.ie-val              { flex: 0 0 auto; min-width: 34px; text-align: right; font-size: 0.85rem; font-variant-numeric: tabular-nums; }
/* Resize: width × height px and %, the standard inputs, sized to their digits. */
.ie-size             { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; color: var(--text-dim); }
.ie-size input       { width: 6.5em; font-variant-numeric: tabular-nums; }
.ie-size input[data-ie=size-pct] { width: 5.5em; margin-left: 8px; }

/* The crop box: a corner sizes it, an edge too (only free), inside moves it;
   the picture outside it goes dark. Chat's own crop (before a photo goes) is
   the same box, with corners only. */
.crop-box       { position: absolute; box-sizing: border-box; border: 2px solid #fff; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55); cursor: move; touch-action: none; }
.crop-box span  { position: absolute; width: 44px; height: 44px; }
.crop-box span::after { content: ""; position: absolute; left: 50%; top: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4); }
.crop-box .nw   { left: -22px;  top: -22px;    cursor: nwse-resize; }
.crop-box .ne   { right: -22px; top: -22px;    cursor: nesw-resize; }
.crop-box .sw   { left: -22px;  bottom: -22px; cursor: nesw-resize; }
.crop-box .se   { right: -22px; bottom: -22px; cursor: nwse-resize; }
.crop-box .n,
.crop-box .s    { left: 50%; margin-left: -22px; cursor: ns-resize; }
.crop-box .e,
.crop-box .w    { top: 50%; margin-top: -22px; cursor: ew-resize; }
.crop-box .n    { top: -22px; }
.crop-box .s    { bottom: -22px; }
.crop-box .e    { right: -22px; }
.crop-box .w    { left: -22px; }
.crop-box :is(.n, .s, .e, .w)::after { width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 3px; }
/* The editor's box: the thirds, to frame by; no edges with a fixed ratio;
   the hand on - the picture moves, the box does not. */
.ie-crop
{
    background:
        linear-gradient(to right, transparent calc(33.333% - 0.5px), rgba(255, 255, 255, 0.45) 0 calc(33.333% + 0.5px), transparent 0 calc(66.667% - 0.5px), rgba(255, 255, 255, 0.45) 0 calc(66.667% + 0.5px), transparent 0),
        linear-gradient(to bottom, transparent calc(33.333% - 0.5px), rgba(255, 255, 255, 0.45) 0 calc(33.333% + 0.5px), transparent 0 calc(66.667% - 0.5px), rgba(255, 255, 255, 0.45) 0 calc(66.667% + 0.5px), transparent 0);
}
.ie-crop.fixed :is(.n, .s, .e, .w) { display: none; }
.ie-crop.is-off                    { pointer-events: none; }
.ie-crop.is-off span               { display: none; }


/* -----------------------------------------------------------------------------
 * LEAFLET CHROME  -  Trips (the route map) and Photos (the "where" map)
 *
 * Leaflet ships its own white popup, zoom buttons and attribution box, which look
 * pasted-on in either scheme. These five rules put them on the Nayive tokens.
 *
 * EVERY selector is scoped under `.leaflet-container`, and that is not decoration:
 * both apps load `lib/.../leaflet.css` AFTER this file, so an equal-specificity
 * rule would lose to Leaflet's own (its link colour is `.leaflet-container a`).
 * The extra class puts these ahead whatever the load order. The `!important`s are
 * Leaflet's own, matched.
 *
 * The map TILES are desaturated per map (`.route-map`, `.lmap`), not here: only
 * the app knows which element is a map, and a print stylesheet wants it too.
 * ---------------------------------------------------------------------------*/
.leaflet-container .leaflet-popup-content-wrapper,
.leaflet-container .leaflet-popup-tip     { background: var(--card2); color: var(--text); }
.leaflet-container a.leaflet-popup-close-button { color: var(--text-dim); }
.leaflet-container .leaflet-control-zoom a { background: var(--card2) !important; color: var(--text) !important;
                                            border-color: var(--line) !important; }
.leaflet-container .leaflet-control-attribution { background: color-mix(in srgb, var(--bg) 78%, transparent) !important;
                                            color: var(--text-dim) !important; }
.leaflet-container .leaflet-control-attribution a { color: var(--text-dim); }


/* -----------------------------------------------------------------------------
 * PHONE  (640px and under)  - the shared half of the phone layout; see TWO
 * LAYOUT MODES in the header. App-specific phone rules live in each app's own
 * @media (max-width: 640px) block.
 * ---------------------------------------------------------------------------*/
@media (max-width: 640px)
{
    /* Fingers, not pointers: the dense 30px toolbar button grows to 36px, and
       the toolbar select grows with it so the row stays one height. */
    .icon-btn.sm       { width: 36px; height: 36px; }
    .tb-select,
    .tb-num            { height: 36px; }
}
/* A text field under 16px makes iOS Safari zoom the whole page in when it gets
   focus - and it never zooms back out. So on a touch phone every field is at
   least 16px. Touch only: a narrow pane on a PC (Tasks inside Planner, a small
   desktop window) is not a phone, and a 16px field there stood taller than
   every other control around it. Attribute selectors: this must beat an app's
   single-class rule (a .search-input, a .scm-num ...). */
@media (max-width: 640px) and (pointer: coarse)
{
    input[type]:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
    input:not([type]),
    select,
    textarea           { font-size: 16px; }
}

/* A phone in either orientation (narrow, or short when turned sideways): a
   dialog taller than the screen scrolls inside (.sheet has overflow-y), and
   its button row sticks to the bottom edge so Save / Cancel / Close are always
   within reach. The negative margins let the strip cover the sheet's own
   padding, so it sits flush with the sheet's bottom edge. Only a row that closes
   the sheet (:last-child) - the launcher's colour-scheme dialog has a Save row in
   the middle of a longer form, and that one must stay in place. */
@media (max-width: 640px), (max-height: 520px)
{
    .sheet .sheet-actions:last-child
    {
        position: sticky;
        bottom: calc(-16px - env(safe-area-inset-bottom));   /* the sticky box is inset by the sheet's padding; this reaches its real edge */
        z-index: 2;
        margin: 18px -16px calc(-16px - env(safe-area-inset-bottom));
        padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
        background: var(--card);
        border-top: 1px solid var(--line);
    }
}

/* THE CLIP'S PANEL (NayiveUI.attachPanel): round coloured buttons, three to a
   row, each with its name under it - Chat's writer and eMail's. Where it sits
   is the app's (Chat: over its writer; eMail: under its clip). */
.attach
{
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px 6px;
    max-width: 420px;
    padding: 22px 10px 18px; border-radius: var(--radius-l);
    background: var(--card); box-shadow: var(--elev-dialog);
}
.att
{
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    border: none; background: none; font: inherit; font-size: 0.8rem; color: var(--text-dim); cursor: pointer;
}
.att .ai
{
    width: 54px; height: 54px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: var(--on-accent); background: var(--c);
}
.att .ai svg   { width: 24px; height: 24px; }
.att:hover .ai { filter: brightness(1.08); }
.att[data-c="0"] { --c: var(--accent); }
.att[data-c="1"] { --c: var(--link); }
.att[data-c="2"] { --c: var(--warn); }
.att[data-c="3"] { --c: var(--danger); }
.att[data-c="4"] { --c: var(--busy); }
.att[data-c="5"] { --c: var(--ok); }

/* A PICTURE YOU CAN CHANGE (.face): a round picture as a button, a small
   camera badge on its corner - Chat's people and profile, Contacts' cards.
   .face.sm: the badge for a list row's small circle. .face-pick: the circle
   on top of a dialog; inside it a .face-box can hold a .face-bin, a red
   trash badge on the lower-left corner, the camera's twin (Chat, Contacts). */
.face { position: relative; flex: 0 0 auto; padding: 0; border: none; border-radius: 50%; background: none; cursor: pointer; }
.face .cam
{
    position: absolute; right: 2px; bottom: 2px; width: 30px; height: 30px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: var(--on-accent); background: var(--accent); border: 2px solid var(--head);
}
.face .cam svg { width: 15px; height: 15px; }
.face:hover > :first-child { filter: brightness(0.95); }
.face.sm .cam     { right: -3px; bottom: -3px; width: 18px; height: 18px; border-width: 1.5px; }
.face.sm .cam svg { width: 10px; height: 10px; }
.face-pick        { display: flex; flex-direction: column; align-items: center; gap: 8px; margin: 2px 0 14px; }
.face-pick .face .cam { border-color: var(--card); }
.face-box         { position: relative; display: inline-flex; }
.face-bin
{
    position: absolute; left: 2px; bottom: 2px; width: 30px; height: 30px; padding: 0; border-radius: 50%;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    color: var(--on-solid); background: var(--danger); border: 2px solid var(--card);
}
.face-bin svg   { width: 15px; height: 15px; }
.face-bin:hover { filter: brightness(0.92); }


/* SCREEN LOCKER (shared/locker.js). html.nv-locked hides the page from its
   first paint; the locker is a full-screen modal <dialog> on black - black is
   the lockers' artwork, like a game board, not a theme colour. The password
   box is a .sheet in the middle, shown by a key or a press. */
html.nv-locked body { visibility: hidden; }
.lock-dlg
{
    visibility: visible;
    position: fixed; inset: 0;
    width: 100%; height: 100%; max-width: none; max-height: none;
    margin: 0; padding: 0; border: 0;
    background: #000; overflow: hidden;
    color-scheme: dark;
    outline: none;                /* it takes the focus back when the password box hides */
}
.lock-dlg::backdrop { background: #000; }
.lock-host { position: absolute; inset: 0; overflow: hidden; }
.sheet.lock-card
{
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: min(340px, calc(100% - 32px));
    display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
}
.lock-card input { flex: 1 1 0; min-width: 0; }
.lock-msg { flex-basis: 100%; margin: 0; font-size: 0.85rem; color: var(--danger); }


/* -----------------------------------------------------------------------------
 * ITEM BROWSER  (shared/browser.js)  -  docs/item-browser-plan.md
 *
 * One way to pick things in a list and act on them, in every list app:
 * - .pick-tick: the round tick browser.js puts at the start of each row. With a
 *   mouse it shows on hover (and on a picked row); on touch only while picking
 *   (the list has .is-picking). A card grid places it itself (absolute corner).
 * - .is-selected paints a picked row (.card-row already fills it with accent).
 * - .sel-group: the header .tb-group of the selection (× count, Select all,
 *   every action; .sel-gap starts a new menu section). What does not fit
 *   leaves (.fit-out) into the toolbar's one ⋮ (.fit-more).
 * - .item-menu: the one popup menu (right-click, long-press, the toolbar ⋮),
 *   the .top-menu look plus a key column and greyed rows.
 * - .ntree: the shared tree (side pane, "Move to…", the phone's slide-in
 *   sheet: .tree-pane.tree-sheet + .tree-backdrop).
 * - .drop-target / .drop-before / .drop-after / .dragging: drag and drop.
 * ---------------------------------------------------------------------------*/
.pick-tick
{
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    margin: 0;
    border: 1.5px solid var(--text-faint);
    border-radius: 50%;
    background: transparent;
    color: transparent;
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--tr-fast);
}
.pick-tick svg                              { width: 13px; height: 13px; }
.nb-list .is-selected .pick-tick,
.nb-list.is-picking .pick-tick              { opacity: 1; }
.nb-list .is-selected .pick-tick            { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.nb-list .card-row.is-selected .pick-tick   { background: var(--on-accent); border-color: var(--on-accent); color: var(--accent); }
.nb-list.is-picking .pick-tick              { border-color: var(--text-dim); }

@media (hover: hover) and (pointer: fine)
{
    .nb-list [aria-selected]:hover .pick-tick    { opacity: 1; }
}
@media (hover: none), (pointer: coarse)
{
    .nb-list:not(.is-picking) .pick-tick         { display: none; }
}

/* The keyboard's row. */
.nb-list [aria-selected]:focus               { outline: none; }
.nb-list [aria-selected]:focus-visible       { outline: 2px solid color-mix(in srgb, var(--accent) 70%, var(--text)); outline-offset: -2px; }

/* The selection group in the header: the count with its ×. The × clears,
   the number scrolls to the next pick. */
.sel-count
{
    display: inline-flex;
    align-items: center;
    flex: none;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.sel-count > .icon-btn
{
    width: auto;
    padding: 0 4px 0 7px;
    border-radius: var(--radius-pill);
    color: var(--text);
    font: inherit;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.sel-count > .icon-btn + .icon-btn           { padding: 0 10px 0 4px; min-width: 2em; }
.sel-count svg                               { width: 15px; height: 15px; }
.sel-count > .icon-btn:hover                 { background: color-mix(in srgb, var(--accent) 28%, transparent); }
/* The pick the count chip (or a closed search) just scrolled to. */
.nb-list .nb-flash                           { animation: nb-flash 1.2s ease-out; }
@keyframes nb-flash
{
    0%, 40% { outline: 3px solid var(--text); outline-offset: -3px; }
    100%    { outline: 3px solid transparent; outline-offset: -3px; }
}
/* A button that did not fit (fitBar); a gap between two menu sections. */
.fit-out                                     { display: none !important; }
.sel-group > .sel-gap:not(:first-child)      { margin-left: 6px; }
@media (max-width: 640px)
{
    .topbar:has(.has-selection) > .tb-modes,
    .header:has(.has-selection) > .tb-modes      { display: none; }
}

/* The one popup menu. */
.item-menu                                   { max-height: calc(100vh - 16px); overflow-y: auto; min-width: 220px; max-width: min(320px, calc(100vw - 16px)); }
.item-menu .menu-item                        { padding: 8px 10px; font-size: 0.92rem; }
.item-menu .menu-item[disabled]              { opacity: 0.42; cursor: default; background: none; }
.item-menu .menu-item:focus-visible          { outline: 2px solid var(--accent); outline-offset: -2px; }
.item-menu .mi-label                         { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-menu .mi-key                           { flex: 0 0 auto; padding-left: 18px; color: var(--text-dim); font-size: 0.78rem; }
.item-menu .mi-noicon                        { width: 18px; flex: 0 0 auto; }
@media (hover: none), (pointer: coarse)
{
    .item-menu .menu-item                    { padding: 11px 12px; font-size: 0.95rem; }
    .item-menu .mi-key                       { display: none; }
}

/* The tree. */
.ntree                                       { display: flex; flex-direction: column; gap: 1px; min-width: max-content; }
.ntree .tree-row
{
    display: flex;
    align-items: center;
    gap: 4px;
    min-height: 30px;
    padding: 4px 6px 4px calc(4px + var(--depth, 0) * 16px);
    border-radius: var(--radius-s);
    color: var(--text);
    font-size: 0.87rem;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}
.ntree .tree-row:hover                       { background: color-mix(in srgb, var(--text) 7%, transparent); }
.ntree .tree-row.is-active                   { background: var(--accent); color: var(--on-accent); }
.ntree .tree-row:focus                       { outline: none; }
.ntree .tree-row:focus-visible               { outline: 2px solid color-mix(in srgb, var(--accent) 70%, var(--text)); outline-offset: -2px; }
.ntree .tree-row.is-off                      { opacity: 0.35; cursor: default; }
.ntree .tree-row.is-off:hover                { background: none; }
/* The arrow: a 24 px target (a finger on a tablet), the row no taller. */
.ntree .twisty                               { display: inline-flex; width: 24px; height: 24px; margin: -4px -4px -4px -2px; flex: 0 0 auto; align-items: center; justify-content: center; color: var(--text-dim); transition: transform var(--tr-fast); }
.ntree .twisty svg                           { width: 14px; height: 14px; }
.ntree .twisty.leaf                          { visibility: hidden; }
.ntree .tree-row.is-open > .twisty           { transform: rotate(90deg); }
.ntree .tree-ic                              { display: inline-flex; flex: 0 0 auto; color: var(--text-dim); }
.ntree .tree-ic svg                          { width: 16px; height: 16px; }
.ntree .tree-row.is-active .twisty,
.ntree .tree-row.is-active .tree-ic          { color: inherit; }
.ntree .tree-name                            { flex: 1 1 auto; }
/* .tree-fit: a side pane of fixed width (Music, Movies, Photos, Bookmarks,
   Contacts) - the tree drops its max-content floor and a long name ends in
   "…". Drive's has no .tree-fit: its pane scrolls sideways instead. */
.ntree.tree-fit                              { min-width: 0; }
.ntree.tree-fit .tree-name                   { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ntree .tree-badge
{
    flex: 0 0 auto;
    margin-left: 6px;
    padding: 0 6px;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    font-size: 0.72rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.ntree .tree-row.is-active .tree-badge       { background: color-mix(in srgb, var(--on-accent) 25%, transparent); }

/* "Move to…": the same tree in a dialog. */
.pick-tree
{
    height: min(320px, 50vh);
    overflow: auto;
    margin-bottom: 12px;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
    background: var(--bg);
}

/* Drag and drop. */
.dragging                                    { opacity: 0.4; }
.drop-target                                 { background: color-mix(in srgb, var(--accent) 30%, transparent) !important; outline: 2px solid var(--accent); outline-offset: -2px; }
.drop-before                                 { box-shadow: inset 0 2px 0 var(--accent); }
.drop-after                                  { box-shadow: inset 0 -2px 0 var(--accent); }

/* The phone's slide-in tree. */
.tree-backdrop                               { display: none; }
@media (max-width: 640px)
{
    .tree-pane.tree-sheet
    {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 95;
        display: block;
        width: 82vw;
        max-width: 300px;
        padding-top: 12px;
        overflow: auto;
        background: var(--bg);
        transform: translateX(-100%);
        transition: transform 0.18s ease;
    }
    .tree-pane.tree-sheet.open               { transform: none; box-shadow: 0 0 32px rgba(0, 0, 0, 0.45); }
    .tree-backdrop.open                      { display: block; position: fixed; inset: 0; z-index: 90; background: rgba(0, 0, 0, 0.45); }
    .tree-sheet .ntree                       { min-width: 0; }
    .tree-sheet .ntree .tree-row             { min-height: 40px; }
    .tree-sheet .ntree .tree-name            { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* ---------------------------------------------------------------------------
 * EXPORT TO PDF (print)
 *
 * No PDF library: the page fills its hidden #printRoot with a plain,
 * light-theme sheet and calls window.print(); the user picks "Save as PDF".
 * NayiveUI.printPage (Calendar, Split) sets .printing on <html> until
 * afterprint, so a plain Ctrl+P still prints the page itself. Trips stages its
 * own sheet (trips.css) on top of the same base. Each page adds its own
 * .pr-* parts.
 * ------------------------------------------------------------------------- */
#printRoot { display: none; }

@media print
{
    @page { margin: 14mm; }
    /* An app that locks the page (html, body { height: 100%; overflow: hidden })
       would clip the printed sheet to a single page. */
    .printing, .printing body { height: auto !important; overflow: visible !important; background: #fff !important; }
    .printing #app, .printing .sheet-backdrop, .printing #toast { display: none !important; }
    .printing #printRoot { display: block !important; }
}

#printRoot { color: #111; background: #fff; font: 12px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
#printRoot .pr-empty { color: #666; font-style: italic; }
#printRoot .pr-foot { margin-top: 24px; padding-top: 8px; border-top: 1px solid #bbb; color: #666; font-size: 10.5px; }

/* ---------------------------------------------------------------------------
 * FINGER-SIZED TARGETS (touch screens only)
 *
 * A finger pad is ~10 mm, 40-44 CSS px; Apple asks 44, Google 48. Many controls
 * here draw at 16-36 px, which a mouse hits fine and a thumb misses - and a
 * long press that lands beside a drag grip selected the text next to it.
 *
 * 1. HIT PAD: an invisible ::before grows the area that takes the tap to at
 *    least --hit on each axis, centred. Nothing moves and nothing looks
 *    different: only where a finger counts as "on" the control. :where() keeps
 *    it at zero specificity, so a control with its own position or ::before
 *    keeps them. The svg app icon cannot carry a ::before: padding + an equal
 *    negative margin do the same there.
 * 2. NO TEXT SELECTION on rows that are dragged (a stage's head row, a
 *    sub-task, a queue row, a sheet tab): a long press there means "move",
 *    never "select". Text fields inside keep selecting.
 * 3. Rows that are too dense for (1) - pads would overlap the next row - grow
 *    instead (Tasks' sub-tasks: tasks/index.html).
 *
 * tools/compat-test/targets.mjs lists what is still under 40 px, app by app.
 * ---------------------------------------------------------------------------*/
@media (pointer: coarse)
{
    :root { --hit: 44px; }

    :where(.icon-btn, .pop-btn, .transport-btn, .tb-btn, .fmt-btn, .fmt-color-btn, .pill,
           .text-btn, .info-dot, .task-chevron, .stage-drag, .subtask-handle,
           h1.nayive-home-link, .app-title.nayive-home-link, .detail-heading.collapsible)
    {
        position: relative;
    }
    :where(.icon-btn, .pop-btn, .transport-btn, .tb-btn, .fmt-btn, .fmt-color-btn, .pill,
           .text-btn, .info-dot, .task-chevron, .stage-drag, .subtask-handle,
           h1.nayive-home-link, .app-title.nayive-home-link, .detail-heading.collapsible)::before
    {
        content: "";
        position: absolute;
        inset: min(0px, calc((100% - var(--hit)) / 2));
    }
    svg.app-icon.nayive-home-link             { box-sizing: content-box; padding: 12px; margin: -12px -6px -12px -12px; }   /* right: keeps the icon's 6px */

    :where(.stage-top, .subtask-row, .queue-row, .sheet-tab)
    {
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }
    :where(.stage-top, .subtask-row, .queue-row, .sheet-tab) :is(input, textarea, [contenteditable=""], [contenteditable="true"])
    {
        -webkit-user-select: text;
        user-select: text;
        -webkit-touch-callout: default;
    }

    /* The seek / volume sliders: the bar stays 4 px, the box a thumb can grab
       grows - and an equal negative margin keeps the layout where it was. */
    input[type=range]                         { height: 40px; margin-block: -18px; background: transparent; }
    input[type=range]::-webkit-slider-thumb   { width: 20px; height: 20px; margin-top: -8px; }
    input[type=range]::-moz-range-thumb       { width: 20px; height: 20px; }
}
