/* ═══════════════════════════════════════════════════════════════════════════
   FIT.CSS - the layer that promises text is never cut off.

   Aluma had one bug wearing a hundred faces: a line whose top was shaved off.
   The Streak tile read "Kept up today" with the caps sliced through; the same
   thing happened, by a pixel or by ten, to every widget title and every
   truncated label in the app.

   It is ONE mechanism.

     A font needs more vertical room than its point size. Geist's content area
     - ascender to descender, which is the box the browser actually paints -
     is 1.31em. The design system asked for `--leading-snug: 1.25` and
     `--leading-tight: 1.2`, and around a hundred rules hardcode
     `line-height: 1` on a headline number.

     On its own that is harmless; a tight line box only lets neighbouring
     lines sit closer. It becomes a bug the moment the same element also
     carries `overflow: hidden` - which every single-line truncating label in
     this codebase does, because that is what `text-overflow: ellipsis`
     requires. Now the clip box IS the line box, the line box is shorter than
     the glyphs, and the browser throws the difference away.

     0.06em at 15px is the 1px shave that measures on this machine. In a
     browser that falls back off Geist - a slow school network that times the
     font out, a managed profile, a user stylesheet - the fallback's metrics
     can want half again as much, and the same rule eats a third of the cap
     height. That is the screenshot this file was written from.

   Three defences, weakest to strongest:

     1 · Make the ladder honest, so in the ordinary case nothing needs
         rescuing at all. (Section 1.)

     2 · An ink guard on every element in the app that truncates: vertical
         padding widens the CLIP box, an equal negative margin gives the space
         straight back to the layout. Same painted height, same baseline, same
         grid track - plus 0.22em of slack above and below the glyphs that no
         font can exhaust. (Section 2.)

         `overflow-clip-margin` is the purpose-built property, but only the
         all-sides shorthand ships (no `-block` longhand in any engine today),
         and slack on the inline axis would push truncated text out past its
         own ellipsis. The block axis is the one that needs it, so: padding.

     3 · A floor under anything that could be squeezed, because
         `overflow: hidden` zeroes a flex or grid item's automatic minimum
         size and a short container can then compress a label to nothing
         without ever technically overflowing. (Section 3.)

   Sections 4-6 carry the same promise into the Home board, where the student
   picks the size: a tile dragged to any width or height must hold what is in
   it, and shed detail rather than clip it.

   Loads after every component sheet, before rounded.css. It touches only the
   box text is painted into - never a colour, never a position.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── 1 · Where the values live ───────────────────────────────────────────────
   Nothing is minted here. `--leading-tight`, `--leading-snug`, `--ink-guard`
   and `--ink-floor` are declared in redesign.css, which owns the design
   system - a component sheet that declared its own would fork it, and
   tests/test_beta_redesign.py holds that line on purpose. The two leading
   steps now clear Geist's 1.31em content area; this file spends the guard on
   the elements that would still be clipped without it. */


/* ── 2 · The ink guard ───────────────────────────────────────────────────────
   Every selector below sets `overflow: hidden; text-overflow: ellipsis` to
   keep a name on one line, and so is a box whose clip edge sits exactly on
   its own glyphs. The list is generated from the component sheets rather than
   guessed - if a new rule starts truncating, it belongs here too.

   Excluded on purpose: anything that paints a surface. A chip or a tab has
   vertical padding already, so its clip box was never tight - and growing its
   padding would visibly change the pill. Those get the floor in section 3
   instead.

   Written with `:where()` so it adds no specificity at all. Any component
   rule that wants a different box still wins outright, and nothing later has
   to fight this. */
:where(
  .ac-name,
  .account-card .ac-name,
  .account-popover-user strong,
  .active-project-option .name,
  .ap-card-top .task-subject,
  .ap-row-title,
  .artifact-panel-title,
  .cal-class,
  .cal-goto-item .gi-title,
  .composer-model .model-short,
  .conn-row-desc,
  .dash-act-text > small,
  .dash-act-text strong,
  .dash-assignment-body > small,
  .dash-assignment-body > strong,
  .dash-assignment-body strong,
  .dash-class-card > strong,
  .dash-class-meta,
  .dash-continue-body > small,
  .dash-continue-body > strong,
  .dash-continue-body strong,
  .dv-cards span,
  .dv-chat span,
  .dv-flow-node b,
  .dv-hooks .abs,
  .dv-hooks .real,
  .dv-mail .dv-mailline,
  .dv-math span,
  .dv-mix span,
  .dv-quiz .dv-q,
  .dv-row b,
  .dv-steps .dv-step b,
  .dv-way b,
  .file-item .fi-name,
  .hist-row .title,
  .hub-assignment > div strong,
  .hub-next-action strong,
  .hw-card-meta > strong,
  .hw-card-meta strong,
  .hw-count-body > strong,
  .hw-focus-body > strong,
  .hw-list-main,
  .hw-list-meta,
  .hw-title,
  .listen-heard,
  .lt-title,
  .lumi-head-context,
  .lumi-head-title,
  .lumi-promo-text,
  .mc-topic,
  .mx-next,
  .palette-item .pi-label,
  .palette-item .pi-sublabel,
  .project-card .pc-name,
  .project-item .name,
  .recent-chat-item,
  .recent-preview,
  .recent-title,
  .skill-desc,
  .status-label,
  .task-provenance .tp-also,
  .task-subject,
  .td-material-name,
  .tm-name,
  .tp-dd-value,
  .tp-subtype-name,
  .tt-row-mode,
  .tt-row-title,
  .tt-widget-label,
  .tt-widget-sub,
  .upcoming-item .title,
  .view > .topbar .page-title,
  .view-tasks .task-subject,
  .vs-preview,
  .vs-title,
  .welcome-continue .wc-title
) {
  padding-block: var(--ink-guard);
  margin-block: calc(var(--ink-guard) * -1);
  /* None of these paint a background today. If one is given one later, it
     stays the size the layout says it is rather than growing by the guard. */
  background-clip: content-box;
}


/* ── 3 · Floors, so a squeeze cannot collapse a line ─────────────────────────
   `overflow: hidden` sets a flex or grid item's automatic minimum size to
   zero. On the inline axis that is exactly right - it is how a long class
   name shrinks to an ellipsis instead of pushing its row wide. On the block
   axis it is how a label ends up half a line tall inside a container that ran
   short, which is the other way the app produced a cut-off word. */
:where(
  .ac-name,
  .account-card .ac-name,
  .account-popover-user strong,
  .active-project-option .name,
  .ap-card-top .task-subject,
  .ap-row-title,
  .artifact-panel-title,
  .cal-class,
  .cal-goto-item .gi-title,
  .composer-model .model-short,
  .conn-row-desc,
  .dash-act-text > small,
  .dash-act-text strong,
  .dash-assignment-body > small,
  .dash-assignment-body > strong,
  .dash-assignment-body strong,
  .dash-class-card > strong,
  .dash-class-meta,
  .dash-continue-body > small,
  .dash-continue-body > strong,
  .dash-continue-body strong,
  .dv-cards span,
  .dv-chat span,
  .dv-flow-node b,
  .dv-hooks .abs,
  .dv-hooks .real,
  .dv-mail .dv-mailline,
  .dv-math span,
  .dv-mix span,
  .dv-quiz .dv-q,
  .dv-row b,
  .dv-steps .dv-step b,
  .dv-way b,
  .file-item .fi-name,
  .hist-row .title,
  .hub-assignment > div strong,
  .hub-next-action strong,
  .hw-card-meta > strong,
  .hw-card-meta strong,
  .hw-count-body > strong,
  .hw-focus-body > strong,
  .hw-list-main,
  .hw-list-meta,
  .hw-title,
  .listen-heard,
  .lt-title,
  .lumi-head-context,
  .lumi-head-title,
  .lumi-promo-text,
  .mc-topic,
  .mx-next,
  .palette-item .pi-label,
  .palette-item .pi-sublabel,
  .project-card .pc-name,
  .project-item .name,
  .recent-chat-item,
  .recent-preview,
  .recent-title,
  .skill-desc,
  .status-label,
  .task-provenance .tp-also,
  .task-subject,
  .td-material-name,
  .tm-name,
  .tp-dd-value,
  .tp-subtype-name,
  .tt-row-mode,
  .tt-row-title,
  .tt-widget-label,
  .tt-widget-sub,
  .upcoming-item .title,
  .view > .topbar .page-title,
  .view-tasks .task-subject,
  .vs-preview,
  .vs-title,
  .welcome-continue .wc-title
) {
  min-block-size: var(--ink-floor);
}

/* The surfaces: same guarantee, no change to their padding. A pill with 2px
   of padding and `line-height: 1` is the identical bug wearing a background. */
:where(
  .attach-chip .attach-name,
  .back-btn,
  .cal-chip-class,
  .dash-btn-tutor,
  .dash-due-pill,
  .dash-tutor-chip,
  .hist-filter,
  .hist-row .file-tag,
  .hist-row .project-tag,
  .hw-bar-btn,
  .hw-chip,
  .hw-head-action,
  .lqb-label,
  .mx-go,
  .nav-item > span,
  .pc-count,
  .settings-tab,
  .suggestion-chip,
  .topbar-right .badge,
  .tt-filter,
  .tutor-mode-btn
) {
  min-block-size: var(--ink-floor);
}

/* A headline number carries the tightest leading in the app - 1 to 1.05, so
   the digits sit close to whatever is beside them - and is the most visible
   thing in a tile. Same guarantee, scaled to its much larger font, and small
   enough not to disturb the baseline it is aligned on. */
:where(.hw-metric,
       .hw-count-n > strong,
       .hw-count-n > span,
       .hw-ring > span,
       .dash-act-ico,
       .mx-pct,
       .stat-value) {
  padding-block: 0.1em;
  margin-block: -0.1em;
  min-block-size: 1.16em;
}

/* Two places the app paints a number straight into a circle or a ring, where
   a floor in ems would fight the fixed geometry. They get the guard alone. */
:where(.hw-ring > span, .ac-avatar) { min-block-size: 0; }


/* ── 3b · The same floor, on the axis it was never put on ────────────────────
   Section 3 puts a floor under the BLOCK size because `overflow: hidden`
   zeroes a flex item's automatic minimum. That is only half the sentence:
   the zeroing is on BOTH axes, and the inline one is where a button lives.

   animations.css gives every button `overflow: hidden` so the hover sheen
   has something to sweep inside. From that line on, every button in the app
   was a flex item with no minimum width - and in a row too narrow for all of
   them, it did not wrap and it did not overflow. It compressed past its own
   label and cut it, with the text centred, so BOTH ends went: the assignment
   sheet's "Start guided session" painted as "art guided sess" on an iPhone,
   in a row that had `flex-wrap: wrap` sitting right there unused.

   `min-content` is the honest floor: the longest word, and not one pixel
   more. A label still shrinks - "Start guided session" can come down to the
   width of "guided" - it just wraps to a second line at that point instead of
   losing its ends. Where the row can wrap, it wraps; where it cannot, the
   button overflows visibly rather than lying about what it says.

   Not `.icon-btn`: it holds a glyph, not a sentence, and its width is drawn
   rather than measured. */
:where(.primary-btn, .ghost-btn, .danger-btn, .conn-btn, .seg-btn,
       .mode-btn, .ap-mode-btn, .voice-mode-btn, .trait-opt, .trait-badge) {
  min-inline-size: min-content;
}


/* ═══════════════════════════════════════════════════════════════════════════
   4 · THE BOARD: A TILE MUST HOLD ITS CONTENT AT EVERY SIZE

   home-widgets.css already sheds detail as a tile gets NARROW (`data-size`,
   five bands off a measured width). It had no equivalent for a tile that gets
   SHORT, because height was read off `w.h` - the stored row span - and a span
   is not a length: `--hw-row` is 148px on a laptop and 116px on a phone, so
   "one row tall" was three different amounts of room.

   home_widgets.js now measures height the same way it measures width and
   stamps `data-vsize` (xs / sm / md / lg). This section spends it. The rule
   throughout is the same one the width bands follow: DROP the line, never
   squeeze it. A widget that hid its subtitle is still readable; one that
   shaved it in half is broken.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── one row tall ────────────────────────────────────────────────────────────
   A heading and one thing under it. Everything that is commentary on that one
   thing goes: the second line, the progress meter, the footnote, the row of
   legend chips. */
.hw-widget[data-vsize="xs"] .hw-sub,
.hw-widget[data-vsize="xs"] .hw-foot,
.hw-widget[data-vsize="xs"] .hw-meter-wide,
.hw-widget[data-vsize="xs"] .hw-stat-legend,
.hw-widget[data-vsize="xs"] .hw-count-body > small,
.hw-widget[data-vsize="xs"] .hw-focus-body > small,
.hw-widget[data-vsize="xs"] .hw-body > small,
.hw-widget[data-vsize="xs"] .hw-list-meta,
.hw-widget[data-vsize="xs"] .dash-integrity,
.hw-widget[data-vsize="xs"] .dash-class-meta,
.hw-widget[data-vsize="xs"] .dash-continue-body small,
.hw-widget[data-vsize="xs"] .dash-assignment-body small,
.hw-widget[data-vsize="xs"] .dash-empty span { display: none; }

/* ── two rows tall, or one row on a laptop ───────────────────────────────────
   148px sounds roomy until you subtract a 20px heading and 32px of padding.
   What is left is 96px: three list rows, or one paragraph of explanation and
   nothing else. These are the lines that are commentary rather than content,
   and the tile is more useful without them than scrolling past them. */
.hw-widget[data-vsize="sm"] .dash-integrity,
.hw-widget[data-vsize="sm"] .hw-foot,
.hw-widget[data-vsize="sm"] .dash-empty span { display: none; }

/* The secondary line beside a headline is CAPPED here, not dropped. On the
   Streak tile it is "Best: 2 days - 1 of 12 badges": one line, 20px, and it
   fits. On Today's focus it is "Nothing is due. Practise something you want
   to be surer of." - a sentence, which at 264px wraps to three lines and 74px
   and is what pushed the tile 12px over.

   The difference is not which widget it is, it is how long the sentence
   happens to be, so the rule bounds the height rather than guessing: one
   line, whole words, ellipsis if it runs out. The short line is untouched and
   the long one stops being a paragraph. */
.hw-widget[data-vsize="sm"] :is(.hw-focus-body, .hw-count-body) > small,
.hw-widget[data-vsize="sm"] .hw-sub {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow-wrap: normal;
  word-break: normal;
  padding-block: var(--ink-guard);
  margin-block: calc(var(--ink-guard) * -1);
}

/* An empty state with two ways out of it - "Connect your school" and "Add by
   hand" - stacks them, which is 76px of buttons under a 20px headline. Side
   by side they are 38px, and in a 148px tile that is the difference between
   fitting and scrolling. */
.hw-widget[data-vsize="sm"] .dash-empty,
.hw-widget[data-vsize="md"] .dash-empty {
  /* `.dash-empty` is a grid by default, and `flex-flow` on a grid does
     nothing at all - the display has to change first. */
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: var(--s-2);
}
.hw-widget[data-vsize="sm"] .dash-empty > strong,
.hw-widget[data-vsize="md"] .dash-empty > strong { flex: 1 0 100%; }
.hw-widget[data-vsize="sm"] .dash-empty > button,
.hw-widget[data-vsize="md"] .dash-empty > button {
  margin-top: 0;
  /* At their full size "Connect your school" and "Add by hand" are 270px of
     buttons in a 240px row, so they wrap back into a stack and nothing is
     gained. Compacted they are 228px and both survive on one line - which is
     the whole point of rowing them. */
  padding: 6px 12px;
  font-size: var(--fs-xs);
  min-height: 30px;
}

/* An empty state with TWO ways out of it - To-do offers "Connect your school"
   and "Add by hand" - is 66px of buttons under a 20px headline, and even
   compacted the pair is wider than a 264px tile's row, so they wrap back into
   a stack and nothing is gained.

   In a tile this size two competing calls to action is one too many. The
   first is the primary path and it stays; the second is reachable from the
   screen the first one opens and from the Assignments tab, so it is the one
   that goes. The full pair comes back the moment the tile is given a second
   row. */
.hw-widget[data-vsize="xs"] .dash-empty > button ~ button,
.hw-widget[data-vsize="sm"] .dash-empty > button ~ button { display: none; }

/* At the narrowest band the tile is about 158px across, and "Connect your
   school" is a longer string than that: the button wraps to two lines (47px)
   under a headline that has itself wrapped to two (40px), and 109px of that
   does not go into 96px.

   Two lines for the headline is right - it is the content, and "Nothing
   conn..." would be a worse answer than a second line. So the button is what
   goes. A 158px-wide tile cannot hold a call to action whose label does not
   fit on one line, and the place it leads is one tap away in the sidebar and
   on the tile's own heading. It returns as soon as the tile is widened. */
.hw-widget[data-size="xs"]:is([data-vsize="xs"], [data-vsize="sm"]) .dash-empty > button { display: none; }

/* An empty state is a headline, a sentence and a button, and it is nested one
   or two levels inside the body on the four widgets app.js paints. Matched by
   descendant rather than child for exactly that reason - the direct-child
   version silently missed every one of them. */
.hw-widget[data-vsize="sm"] .dash-empty {
  padding: var(--s-2) var(--s-3);
  gap: var(--s-1);
}

/* The integrity promise ("Aluma opens the assignment read only...") is a
   paragraph, and a paragraph's height is set by the width it is given. In a
   three-column tile it is six lines - taller than the list it introduces.
   home-widgets.css already drops it at the narrowest band; the band above it
   is just as narrow for a paragraph. */
.hw-widget[data-size="sm"] .dash-integrity,
.hw-widget[data-size="md"] .dash-integrity { display: none; }

/* Quick actions is the one widget whose content has a floor of its own: four
   76px tiles that wrap to a second row the moment the widget is narrower than
   about 640px. Two rows is 160px and a one-row-tall tile has 96px, so the
   fourth action landed under the tile's edge.

   home-widgets.css already makes exactly this trade in edit mode - the tiles
   give up their floor and their second line. A short tile needs the same
   thing for the same reason, so it gets the same rule. */
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act,
.hw-widget[data-size="xs"][data-w-type="actions"] .dash-act,
.hw-widget[data-size="sm"][data-w-type="actions"] .dash-act,
.hw-widget[data-size="md"][data-w-type="actions"] .dash-act {
  min-height: 0;
  padding: var(--s-2) var(--s-3);
}
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act-ico,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act-ico,
.hw-widget[data-size="xs"][data-w-type="actions"] .dash-act-ico,
.hw-widget[data-size="sm"][data-w-type="actions"] .dash-act-ico,
.hw-widget[data-size="md"][data-w-type="actions"] .dash-act-ico {
  width: 32px;
  height: 32px;
}
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act-text strong,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act-text strong,
.hw-widget[data-size="xs"][data-w-type="actions"] .dash-act-text strong,
.hw-widget[data-size="sm"][data-w-type="actions"] .dash-act-text strong,
.hw-widget[data-size="md"][data-w-type="actions"] .dash-act-text strong {
  -webkit-line-clamp: 1;
  line-clamp: 1;
}

/* Width matters here for the same reason height does, and it bit on a small
   laptop rather than a phone: `.dash-actions` asks for `minmax(140px, 1fr)`
   tracks, so a three-column tile on a 768px viewport resolves to ONE column
   and stacks all four actions - 304px of content in a 180px tile. Narrow
   enough to stack is narrow enough to be a row rather than a card, which is
   the compact form above. */

/* Even compacted, four tiles in two rows are 122px and a one-row-tall widget
   has 112. The last thing left to take is the STACK inside each tile: the
   eyebrow above the headline becomes the eyebrow beside it, and a tile that
   was two lines is one. Both words survive, which is the whole rule of this
   file - shed the arrangement, never the content. */
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act-text,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act-text {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act-text > small,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act-text > small {
  flex: 0 0 auto;
  white-space: nowrap;
}
.hw-widget[data-vsize="xs"][data-w-type="actions"] .dash-act-text > strong,
.hw-widget[data-vsize="sm"][data-w-type="actions"] .dash-act-text > strong {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-line-clamp: 1;
  line-clamp: 1;
}

/* The sentence under an empty state's headline. home-widgets.css drops it at
   `sm` but not at `xs`, which is the band a tile reaches by being dragged
   narrow - so the narrowest tile in the app was the one still trying to wrap
   a two-line sentence into 110px. */
.hw-widget[data-size="xs"] .dash-empty span,
.hw-widget[data-size="sm"] .dash-empty span,
.hw-widget[data-size="md"][data-vsize="sm"] .dash-empty span,
.hw-widget[data-size="md"][data-vsize="md"] .dash-empty span { display: none; }

/* An empty state in the SHORTEST tile stops being a stacked block and becomes
   a row: headline on the left, the action that fixes the emptiness on the
   right. Both survive, in the height one of them used to take.

   Only at `xs`. At `sm` there is room for the stacked form once its sentence
   is dropped (below), and the row form there squeezed the headline hard
   enough to ellipsise it - "No test date set" became "No test da...", which
   is a worse answer than the one it was fixing. */
.hw-widget[data-vsize="xs"] .dash-empty {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
/* The headline gets the room the button does not want, and takes ONE line of
   it. `min-width: 0` alone was not enough: it let the headline shrink to a
   few pixels, and `.hw-body`'s `overflow-wrap: anywhere` then broke
   "No assignments yet" into a column of single letters - a 220px-tall
   headline inside an 87px tile. One line, whole words, ellipsis if it runs
   out; the same contract every other name in a tile has. */
.hw-widget[data-vsize="xs"] .dash-empty > strong {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow-wrap: normal;
  word-break: normal;
  padding-block: var(--ink-guard);
  margin-block: calc(var(--ink-guard) * -1);
}
.hw-widget[data-vsize="xs"] .dash-empty > button {
  flex: 0 0 auto;
  margin-top: 0;
  white-space: nowrap;
}

/* Four action tiles in two rows need 128px of the 122px a one-row tile has.
   The gap between them is the cheapest 12px on the tile. */
.hw-widget[data-vsize="xs"] .dash-actions,
.hw-widget[data-vsize="sm"] .dash-actions { gap: var(--s-2); }

/* The tile's own padding is the other thing that does not scale. `--s-4` top
   and bottom is 32px of a 116px tile - more than a list row - so a short tile
   trades some of it back for a line of content. */
.hw-widget[data-vsize="xs"] .hw-widget-inner {
  gap: var(--s-2);
  padding-block: var(--s-2);
}
.hw-widget[data-vsize="sm"] .hw-widget-inner { gap: var(--s-2); }

/* A heading is 20px of a 116px tile. At the shortest height it keeps its
   words and gives up its size. */
.hw-widget[data-vsize="xs"] .hw-title { font-size: var(--fs-sm); }

/* The headline number is the point of a Streak or a countdown tile, so it is
   the last thing to shrink - but a 36px digit in a 116px box leaves no room
   for the words beside it. */
.hw-widget[data-vsize="xs"] .hw-count-n > strong { font-size: 24px; }
.hw-widget[data-vsize="xs"] .hw-metric { font-size: 24px; }
.hw-widget[data-vsize="xs"] .hw-ring { width: 38px; height: 38px; }
.hw-widget[data-vsize="xs"] .hw-ring > span { width: 28px; height: 28px; font-size: 10px; }

/* A wide button under a one-row tile has nowhere to be. The row above it is
   the content; the button is a shortcut to a view that is one click away in
   the sidebar anyway. */
.hw-widget[data-vsize="xs"] .hw-btn-wide { display: none; }

/* An empty state is a headline, a sentence and a button. In a one-row tile
   only the headline fits, and the tile still has to say which widget it is. */
.hw-widget[data-vsize="xs"] .dash-empty {
  padding: var(--s-2);
  gap: 0;
}
.hw-widget[data-vsize="xs"] .dash-empty button { padding: 4px var(--s-2); }


/* ── 5 · The body is a scroller of last resort, not a plan ───────────────────
   `.hw-body` carries `overflow: auto` so that content which outgrows a tile is
   reachable rather than lost. That is a safety net and it should read as one:
   a tile whose list is one row too long should scroll, and a tile whose LAYOUT
   does not fit should have shed something before it got here.

   Two things it must never do. It must not scroll sideways - a horizontal
   scrollbar inside a 200px tile is unusable and the content behind it is
   unreachable in practice. And it must not let a single long word push that
   scrollbar into existence. */
.hw-body {
  overflow-x: hidden;
  overflow-y: auto;
}

/* Grid and flex children inside a tile inherit `min-width: auto`, which is
   what lets a long word set the track's width and push the tile wide. Every
   container in a tile gives that up: the track collapses and the word wraps
   or ellipsises, which is what the width bands assume. */
.hw-widget :where(ul, ol, li, div, p, section, header, footer) { min-width: 0; }

/* The rows the widgets are built from. A row holds a name and a meta, and the
   name is what shrinks - stated here because the same markup is reused by
   four sheets and the flex defaults disagree with all of them. */
.hw-list-row > .hw-list-main { min-width: 0; flex: 1 1 auto; }
.hw-list-row > .hw-list-meta,
.hw-list-row > .hw-list-grade { flex: 0 0 auto; }


/* ── 6 · The tile itself ─────────────────────────────────────────────────────
   The board's grid tracks are `minmax(0, 1fr)`, so a tile can be narrower
   than its content wants. That only works if the tile agrees to be. */
.hw-widget,
.hw-widget-inner { min-width: 0; min-height: 0; }

/* A tile is a rounded surface that clips, and its inner box inherits the
   radius so clipped content follows the same curve the border draws. Both of
   those were already true; what was missing is that the corner radius has to
   come DOWN on a small tile, or a 24px radius on a 116px-tall tile eats its
   own content area. */
.hw-widget[data-vsize="xs"],
.hw-widget[data-size="xs"] { border-radius: var(--dash-radius-sm); }


/* ── Quick actions in a tile that is short ───────────────────────────────────
   The last case, and the one the width bands and the height bands each solve
   only half of. `.dash-actions` asks for `minmax(140px, 1fr)` tracks, so any
   tile under about 300px resolves to ONE column and all four actions stack:
   213px of content in a 112px tile, even with every tile already compacted to
   a single line.

   There is nothing left to shed inside a tile, so the GRID gives way. Two
   fixed columns puts the four actions in two rows - 102px, which fits - and
   the column count stops depending on a track minimum that a resizable tile
   was never going to respect.

   Under about 320px the two resulting tiles are ~150px each, and at that
   width the icon, the chevron and the eyebrow above the headline all cost
   more than they tell. The headline takes the tile on its own. */
.hw-widget[data-w-type="actions"]:is([data-vsize="xs"], [data-vsize="sm"]) .dash-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.hw-widget[data-w-type="actions"]:is([data-vsize="xs"], [data-vsize="sm"]):is([data-size="xs"], [data-size="sm"], [data-size="md"]) .dash-act {
  grid-template-columns: minmax(0, 1fr);
  padding: var(--s-2);
}
.hw-widget[data-w-type="actions"]:is([data-vsize="xs"], [data-vsize="sm"]):is([data-size="xs"], [data-size="sm"], [data-size="md"]) .dash-act-ico,
.hw-widget[data-w-type="actions"]:is([data-vsize="xs"], [data-vsize="sm"]):is([data-size="xs"], [data-size="sm"], [data-size="md"]) .dash-act-go,
.hw-widget[data-w-type="actions"]:is([data-vsize="xs"], [data-vsize="sm"]):is([data-size="xs"], [data-size="sm"], [data-size="md"]) .dash-act-text > small {
  display: none;
}
