/* Buttons, inputs, badges, cards, icon tiles, navigation, modals and toasts. All values come from tokens.css. */

/* Icons
   Lucide swaps each <i data-lucide> for an <svg class="lucide">. A CSS stroke-width beats the
   attribute Lucide writes, so the 1.75 stroke is set once here instead of on every call. */

.lucide {
  flex-shrink: 0;
  width: var(--icon-size);
  height: var(--icon-size);
  stroke-width: var(--icon-stroke);
}

/* Reserve the icon's space before the script runs, so nothing shifts when it does. */
i[data-lucide] {
  display: inline-block;
  flex-shrink: 0;
  width: var(--icon-size);
  height: var(--icon-size);
}

/* Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  min-height: var(--control-height);
  padding: 0 var(--space-24);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

@media (prefers-reduced-motion: no-preference) {
  .btn:not(.btn-icon):hover {
    transform: translateY(calc(var(--lift) * -1));
  }

  /* Pressing settles the button back down, which reads as a click. */
  .btn:not(.btn-icon):active {
    transform: none;
  }
}

.btn .lucide,
.btn i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.btn-primary {
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

.btn-primary:hover {
  background-color: var(--color-primary-hover);
  color: var(--color-on-primary);
}

.btn-secondary {
  background-color: var(--color-surface);
  border-color: var(--color-border);
  color: var(--color-ink);
}

.btn-secondary:hover {
  background-color: var(--color-bg);
  color: var(--color-ink);
}

/* For actions that remove or delete. The bright --color-danger is too light behind white text. */
.btn-danger {
  background-color: var(--color-danger-text);
  color: var(--color-on-primary);
}

.btn-danger:hover {
  background-color: var(--color-danger-hover);
  color: var(--color-on-primary);
}

.btn-sm {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-16);
  border-radius: var(--radius-sm);
}

/* The main calls to action on marketing pages. Inside the app, buttons stay 44px. */
.btn-lg {
  min-height: var(--control-height-lg);
  font-size: var(--text-md);
}

/* A white button for coloured panels, such as the gradient call to action. */
.btn-inverse {
  background-color: var(--color-surface);
  color: var(--color-primary);
}

.btn-inverse:hover {
  background-color: var(--color-primary-soft);
  color: var(--color-primary-hover);
}

/* Square button holding one icon, such as a close button. Always give it an aria-label. */
.btn-icon {
  width: var(--control-height-sm);
  min-height: var(--control-height-sm);
  padding: 0;
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: var(--color-muted);
}

.btn-icon:hover {
  background-color: var(--color-bg);
  color: var(--color-ink);
}

.btn-icon .lucide,
.btn-icon i[data-lucide] {
  width: var(--icon-size);
  height: var(--icon-size);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-block {
  width: 100%;
}

.btn img {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

/* A button that reads as a link, for small actions such as "Forgot password?". */
.btn-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-primary);
  font-size: inherit;
  font-weight: var(--weight-semibold);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
}

.btn-link:hover {
  color: var(--color-primary-hover);
}

/* Form fields */

.field {
  display: grid;
  gap: var(--space-8);
}

.field-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-ink);
}

.field-hint {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.field-error {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--color-danger-text);
}

.field-error .lucide,
.field-error i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.input {
  width: 100%;
  min-height: var(--control-height);
  padding: 0 var(--space-12);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-sm);
  color: var(--color-ink);
  /* 16px keeps iOS Safari from zooming in when the field gets focus, even inside the 14px app. */
  font-size: var(--text-md);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

textarea.input {
  padding: var(--space-12);
  line-height: var(--leading-normal);
  resize: vertical;
}

.input::placeholder {
  color: var(--color-muted);
  opacity: 1;
}

/* A transparent outline stays invisible normally but shows in Windows high-contrast mode, where box-shadow is dropped. */
.input:focus {
  outline: 2px solid transparent;
  border-color: var(--color-primary);
  box-shadow: var(--focus-ring);
}

.input[aria-invalid="true"] {
  border-color: var(--color-danger);
}

.input[aria-invalid="true"]:focus {
  border-color: var(--color-danger);
  box-shadow: var(--focus-ring-danger);
}

.input:disabled {
  background-color: var(--color-bg);
  color: var(--color-muted);
  cursor: not-allowed;
}

/* Badges
   Status mapping: In review → badge-primary, Approved → badge-success, Changes requested → badge-warning. */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-xs);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  white-space: nowrap;
}

.badge-primary {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.badge-success {
  background-color: var(--color-success-soft);
  color: var(--color-success-text);
}

.badge-warning {
  background-color: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.badge-danger {
  background-color: var(--color-danger-soft);
  color: var(--color-danger-text);
}

.badge-purple {
  background-color: var(--color-purple-soft);
  color: var(--color-purple-text);
}

.badge-neutral {
  background-color: var(--color-bg);
  color: var(--color-text);
}

/* Cards and panels */

.card {
  padding: var(--space-24);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

.panel {
  padding: var(--space-24);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
}

@media (min-width: 768px) {
  .panel {
    padding: var(--space-32);
  }
}

/* Icon tiles */

.icon-tile {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: var(--icon-tile-size);
  height: var(--icon-tile-size);
  border-radius: var(--radius-md);
}

.icon-tile-primary {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.icon-tile-success {
  background-color: var(--color-success-soft);
  color: var(--color-success);
}

.icon-tile-warning {
  background-color: var(--color-warning-soft);
  color: var(--color-warning);
}

.icon-tile-danger {
  background-color: var(--color-danger-soft);
  color: var(--color-danger);
}

.icon-tile-purple {
  background-color: var(--color-purple-soft);
  color: var(--color-purple);
}

/* Sidebar navigation
   The sidebar is a white panel. Mark the current page's link with aria-current="page". */

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  padding: var(--space-16);
  background-color: var(--color-surface);
  border-right: var(--border);
}

.nav-list {
  display: grid;
  gap: var(--space-4);
  padding: 0;
  list-style: none;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  min-height: var(--control-height);
  padding: 0 var(--space-12);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.nav-item:hover {
  background-color: var(--color-bg);
  color: var(--color-ink);
}

/* "page" marks the current page; "true" marks the section a page belongs to, such as Folders inside a folder. */
.nav-item[aria-current="page"],
.nav-item[aria-current="true"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

/* Modal
   A native <dialog> opened with showModal(), which traps focus and closes on Escape. */

.modal {
  width: min(var(--modal-width), calc(100% - 2 * var(--space-16)));
  max-height: calc(100% - 2 * var(--space-16));
  /* The margin reset in base.css removes the browser's auto margin, which is what centres a modal dialog. */
  margin: auto;
  padding: 0;
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-raised);
  color: var(--color-text);
}

.modal::backdrop {
  background-color: var(--color-overlay);
}

.modal[open] {
  animation: modal-in var(--duration-base) var(--ease-out);
}

.modal[open]::backdrop {
  animation: fade-in var(--duration-base) var(--ease-out);
}

/* Padding sits on this inner element so that a click landing on the <dialog> itself can only be a backdrop click. */
.modal-body {
  display: grid;
  gap: var(--space-16);
  padding: var(--space-24);
}

.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-16);
}

.modal-header h2 {
  font-size: var(--text-xl);
}

.modal-header .btn-icon {
  margin: calc(-1 * var(--space-8)) calc(-1 * var(--space-8)) 0 0;
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-12);
  padding-top: var(--space-8);
}

@media (max-width: 479px) {
  .modal-actions .btn {
    flex: 1 1 0;
  }
}

/* Share dialog: the link in a read-only field, with its Copy button beside it. */

.share-area {
  display: grid;
  gap: var(--space-16);
}

.share-link-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-8);
}

.share-link-input {
  text-overflow: ellipsis;
}

/* Password and Expiry date: a row each, with its buttons beside it, or below it on narrow screens.
   Changing one swaps its row for a small form. */
.share-setting,
.share-setting-form {
  padding-top: var(--space-16);
  border-top: var(--border);
}

.share-setting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
}

.share-setting-title {
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.share-setting-value {
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.share-setting-actions {
  display: flex;
  gap: var(--space-8);
}

.share-setting-form {
  display: grid;
  gap: var(--space-8);
}

.share-password-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-8);
}

/* Always on the page so screen readers announce each message; while empty it takes no room. */
.share-status {
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.share-status:empty {
  margin-top: calc(-1 * var(--space-16));
}

/* Share folder dialog: a small form to add a person, then everyone the folder is shared with, one
   row each: avatar, name and email, and a Remove button. On phones the button sits under the name
   and email, which then have the full width; from 480px it sits beside them. */

.access-form {
  display: grid;
  gap: var(--space-16);
}

.access-heading {
  padding-top: var(--space-16);
  border-top: var(--border);
  font-size: var(--text-md);
}

.access-list {
  display: grid;
  gap: var(--space-12);
  padding: 0;
  list-style: none;
}

.access-person {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-8) var(--space-12);
}

.access-person .btn {
  grid-column: 2;
  justify-self: start;
}

@media (min-width: 480px) {
  .access-person {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .access-person .btn {
    grid-column: auto;
  }
}

.access-person-info {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.access-person-name {
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.access-person-email {
  color: var(--color-muted);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

/* Toasts
   Toasts stack in a fixed live region: bottom centre on phones, bottom right from 768px. */

.toast-region {
  position: fixed;
  z-index: var(--z-toast);
  right: var(--space-16);
  bottom: var(--space-16);
  left: var(--space-16);
  display: grid;
  justify-items: center;
  gap: var(--space-8);
  /* The empty region must not block clicks on the page under it. */
  pointer-events: none;
}

@media (min-width: 768px) {
  .toast-region {
    right: var(--space-24);
    bottom: var(--space-24);
    left: auto;
    width: var(--toast-width);
  }
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  width: 100%;
  max-width: var(--toast-width);
  padding: var(--space-8) var(--space-8) var(--space-8) var(--space-16);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  pointer-events: auto;
}

.toast-region .toast {
  animation: toast-in var(--duration-base) var(--ease-out);
}

.toast-message {
  flex: 1;
  padding-block: var(--space-8);
}

.toast-success .toast-icon { color: var(--color-success); }
.toast-error .toast-icon { color: var(--color-danger); }
.toast-info .toast-icon { color: var(--color-primary); }

/* Logo: the mark and the product name, linking home */

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-12);
  color: var(--color-ink);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
}

.brand:hover {
  color: var(--color-ink);
}

.brand-mark {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: var(--logo-size);
  height: var(--logo-size);
  border-radius: var(--radius-sm);
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

/* Site footer, on the home page and the plain text pages */

.site-footer {
  padding-block: var(--space-64) var(--space-32);
  background: linear-gradient(to bottom, var(--color-bg), var(--color-surface));
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-24);
  padding: 0;
  list-style: none;
}

.site-footer-links a {
  color: var(--color-muted);
}

.site-footer-links a:hover {
  color: var(--color-ink);
}

/* Plain text pages (privacy, terms, contact): the logo, one card of text, and the site footer,
   which stays at the bottom of the screen when the text is short. */

.text-page {
  display: grid;
  grid-template-rows: auto 1fr auto;
}

.text-page-header {
  display: flex;
  align-items: center;
  min-height: var(--space-64);
  padding-block: var(--space-12);
}

.text-page-main {
  padding-block: var(--space-32);
}

.text-page-card {
  display: grid;
  gap: var(--space-16);
  max-width: var(--text-width);
  margin-inline: auto;
}

@media (min-width: 768px) {
  .text-page-main {
    padding-block: var(--space-48);
  }

  .text-page-card {
    padding: var(--space-48);
  }
}

/* Forms */

.form {
  display: grid;
  gap: var(--space-16);
}

.field-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-8);
  font-size: var(--text-sm);
}

/* A message about a whole form or page. Errors use role="alert", so screen readers announce them. */
.alert {
  padding: var(--space-12) var(--space-16);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.alert-error {
  background-color: var(--color-danger-soft);
  color: var(--color-danger-text);
}

.alert-success {
  background-color: var(--color-success-soft);
  color: var(--color-success-text);
}

.alert-warning {
  background-color: var(--color-warning-soft);
  color: var(--color-warning-text);
}

/* A labelled rule between two ways of doing the same thing, such as "or". */
.divider {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.divider::before,
.divider::after {
  content: '';
  flex: 1;
  border-top: var(--border);
}

/* Loading, empty and error states */

.spinner {
  display: inline-block;
  flex-shrink: 0;
  width: var(--icon-size);
  height: var(--icon-size);
  border: 2px solid var(--color-primary-soft);
  border-top-color: var(--color-primary);
  border-radius: var(--radius-pill);
  animation: spin var(--duration-spin) linear infinite;
}

.state {
  display: grid;
  justify-items: center;
  gap: var(--space-12);
  padding: var(--space-48) var(--space-24);
  text-align: center;
}

.state h2,
.state h3 {
  font-size: var(--text-lg);
}

.state p {
  max-width: 40ch;
  color: var(--color-text);
}

/* Login and sign-up pages: one centred card */

.auth-layout {
  display: grid;
  align-content: start;
  justify-items: center;
  gap: var(--space-32);
  min-height: 100vh;
  padding: var(--space-48) var(--space-16);
}

.auth-card {
  display: grid;
  gap: var(--space-24);
  width: 100%;
  max-width: var(--form-width);
}

.auth-header {
  display: grid;
  gap: var(--space-8);
}

.auth-header h1 {
  font-size: var(--text-2xl);
}

.auth-footer {
  color: var(--color-text);
  font-size: var(--text-sm);
  text-align: center;
}

@media (min-width: 640px) {
  .auth-layout {
    align-content: center;
    padding-block: var(--space-64);
  }

  .auth-card {
    padding: var(--space-32);
  }
}

/* Account menu: a button that opens a small panel */

.avatar {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: var(--control-height-sm);
  height: var(--control-height-sm);
  border-radius: var(--radius-pill);
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}

.menu {
  position: relative;
}

.menu-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
  transition: background-color var(--duration-fast) var(--ease-out);
}

.menu-toggle:hover {
  background-color: var(--color-bg);
}

.menu-toggle .lucide,
.menu-toggle i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  color: var(--color-muted);
}

.menu-panel {
  position: absolute;
  top: calc(100% + var(--space-8));
  right: 0;
  z-index: 20;
  display: grid;
  gap: var(--space-4);
  width: var(--menu-width);
  padding: var(--space-8);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
}

.menu-heading {
  padding: var(--space-8) var(--space-12);
  color: var(--color-muted);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  width: 100%;
  min-height: var(--control-height);
  padding: 0 var(--space-12);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.menu-item:hover {
  background-color: var(--color-bg);
  color: var(--color-ink);
}

.menu-item .lucide,
.menu-item i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

/* A link in a menu to the page that is already showing, as in the phone's More menu. */
.menu-item[aria-current="page"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

/* App shell
   Phones: a top bar, the content, and the sidebar's links as a bar fixed to the bottom.
   From 960px: the sidebar sits beside the content. */

.app-shell {
  min-height: 100vh;
  padding-bottom: var(--bottom-nav-height);
}

.app-sidebar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 10;
  padding: var(--space-8);
  border-top: var(--border);
  border-right: 0;
}

.app-sidebar .brand {
  display: none;
}

/* Up to five links share the bar's width equally, each with its name under its icon. A name too long
   for its share, such as My documents on a 360px phone, takes two lines. */
.app-sidebar .nav-list {
  display: flex;
  justify-content: center;
  gap: var(--space-4);
}

.app-sidebar .nav-list > li {
  flex: 1 1 0;
  min-width: 0;
  max-width: var(--bottom-nav-item-max);
}

/* More: when the sidebar has more than five links, the list is marked data-crowded. The bar then
   keeps the first four and shows a More button as its fifth; the links marked data-nav-extra leave
   the bar and are listed in the menu More opens, which rises above the bar. js/app-shell.js marks
   the list and fills the menu. From 960px the sidebar shows every link and has no More. */
.nav-more {
  display: none;
}

.app-sidebar .nav-list[data-crowded] > .nav-more {
  display: block;
}

.app-sidebar .nav-list[data-crowded] > [data-nav-extra] {
  display: none;
}

.nav-more-toggle {
  width: 100%;
  border: 0;
  background: none;
}

.nav-more-panel {
  top: auto;
  bottom: calc(100% + var(--space-8));
}

.app-sidebar .nav-item {
  flex-direction: column;
  justify-content: center;
  gap: var(--space-4);
  height: 100%;
  min-height: var(--control-height);
  padding: var(--space-4) 0;
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
  text-align: center;
}

.app-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  min-height: var(--space-64);
  padding: 0 var(--space-16);
  background-color: var(--color-surface);
  border-bottom: var(--border);
}

.app-workspace {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-user-name {
  display: none;
}

/* Search, in the top bar. On a phone the bar shows only a button for it; the button opens the box
   over the bar, and what is found lists under the bar across the screen. From 640px the box is
   always in the bar, and what is found lists under the box. */
.app-topbar {
  position: relative;
}

.app-search {
  display: flex;
  align-items: center;
}

.app-search-field {
  display: none;
  align-items: center;
  gap: var(--space-8);
  min-height: var(--search-height);
  padding: 0 var(--space-4) 0 var(--space-12);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-sm);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.app-search-field:focus-within {
  border-color: var(--color-primary);
  box-shadow: var(--focus-ring);
}

.app-search-field > .lucide,
.app-search-field > i[data-lucide] {
  flex-shrink: 0;
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  color: var(--color-muted);
}

/* The box around it shows the focus, so the field itself needs no outline of its own. */
.app-search-input {
  flex: 1;
  min-width: 0;
  padding: 0;
  background: none;
  border: 0;
  outline: none;
  color: var(--color-ink);
  /* 16px keeps iOS Safari from zooming in when the field gets focus. */
  font-size: var(--text-md);
}

.app-search-input::placeholder {
  color: var(--color-muted);
  opacity: 1;
}

.app-search.is-open .app-search-field {
  position: absolute;
  inset: var(--space-12) var(--space-16);
  z-index: 5;
  display: flex;
}

.app-search-results {
  top: 100%;
  right: var(--space-16);
  left: var(--space-16);
  width: auto;
  max-height: var(--search-results-height);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.search-list {
  display: grid;
  gap: var(--space-4);
  padding: 0;
  list-style: none;
}

.search-item {
  padding-block: var(--space-8);
}

.search-item .icon-tile {
  flex-shrink: 0;
}

.search-item-text {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.search-item-name,
.search-item-detail {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-item-name {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
}

.search-item-detail {
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
}

@media (min-width: 640px) {
  .app-search {
    position: relative;
  }

  .app-search-open,
  .app-search-close {
    display: none;
  }

  .app-search-field,
  .app-search.is-open .app-search-field {
    position: static;
    display: flex;
    width: var(--search-width);
    padding-right: var(--space-12);
  }

  .app-search-results {
    top: calc(100% + var(--space-8));
    right: 0;
    left: auto;
    width: var(--search-results-width);
    max-width: calc(100vw - var(--space-32));
  }
}

/* Notifications: the bell in the top bar with its unread count, and the list it opens. On a phone
   the list spans the screen under the bar, as search results do; from 640px it hangs under the
   bell. js/notifications.js builds all of it. */
.app-bell {
  position: static;
}

.bell-toggle {
  position: relative;
}

.bell-count {
  position: absolute;
  top: calc(var(--space-4) * -1);
  right: calc(var(--space-4) * -1);
  display: grid;
  place-items: center;
  min-width: var(--bell-count-size);
  height: var(--bell-count-size);
  padding: 0 var(--space-4);
  border-radius: var(--radius-pill);
  background-color: var(--color-danger-text);
  box-shadow: var(--ring-surface);
  color: var(--color-on-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}

.bell-panel {
  top: 100%;
  right: var(--space-16);
  left: var(--space-16);
  gap: 0;
  width: auto;
  padding: 0;
  overflow: hidden;
}

.bell-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  min-height: var(--control-height);
  padding: var(--space-8) var(--space-16);
  border-bottom: var(--border);
}

.bell-title {
  font-size: var(--text-md);
}

.bell-list {
  display: grid;
  gap: var(--space-4);
  max-height: var(--bell-list-height);
  margin: 0;
  padding: var(--space-8);
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

/* One notification: a link to what it is about and, while it is unread, a button that marks it read. */
.bell-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.bell-link {
  flex: 1;
  align-items: flex-start;
  width: auto;
  min-width: 0;
  padding-block: var(--space-8);
  font-weight: var(--weight-regular);
}

.bell-text {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.bell-line {
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.bell-line strong {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
}

/* A comment's own words: two lines of them at most. */
.bell-quote {
  display: -webkit-box;
  overflow: hidden;
  color: var(--color-muted);
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.bell-time {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--color-muted);
  font-size: var(--text-xs);
}

/* Unread: a dot before the time, and the button beside the notification. */
.bell-dot {
  flex: none;
  width: var(--bell-dot-size);
  height: var(--bell-dot-size);
  border-radius: var(--radius-pill);
  background-color: var(--color-primary);
}

.bell-read {
  flex: none;
  margin-top: var(--space-8);
}

/* Loading, nothing yet, or what went wrong with a button to try again. */
.bell-note {
  display: grid;
  justify-items: center;
  gap: var(--space-12);
  padding: var(--space-24) var(--space-16);
  color: var(--color-muted);
  text-align: center;
}

@media (min-width: 640px) {
  .app-bell {
    position: relative;
  }

  .bell-panel {
    top: calc(100% + var(--space-8));
    right: 0;
    left: auto;
    width: var(--bell-panel-width);
    max-width: calc(100vw - var(--space-32));
  }
}

.app-content {
  display: grid;
  align-content: start;
  gap: var(--space-24);
  padding: var(--space-24) var(--space-16);
}

.app-page {
  display: grid;
  gap: var(--space-24);
}

/* A page title with its main action beside it, which wraps below on narrow screens. */
.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-16);
}

.page-header p {
  margin-top: var(--space-4);
  color: var(--color-text);
}

.page-actions,
.section-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8) var(--space-12);
}

.section-row {
  justify-content: space-between;
}

/* Breadcrumb: where a page sits, with the current page last */

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-8);
  padding: 0;
  list-style: none;
  color: var(--color-muted);
}

.breadcrumb li + li::before {
  content: '/';
  /* The empty alternative keeps screen readers from announcing the separator. */
  content: '/' / '';
  margin-right: var(--space-8);
}

.breadcrumb a {
  color: var(--color-text);
  text-decoration: none;
}

.breadcrumb a:hover {
  color: var(--color-primary);
  text-decoration: underline;
}

.breadcrumb [aria-current="page"] {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

/* Folder cards. The whole card opens the folder through the name's link, stretched over the card;
   the card's own buttons sit above that link. */

.folder-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
  gap: var(--space-16);
  padding: 0;
  list-style: none;
}

.folder-card {
  position: relative;
  display: grid;
  align-content: start;
  gap: var(--space-4);
  padding: var(--space-16);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.folder-card:hover {
  border-color: var(--color-primary-light);
  box-shadow: var(--shadow-raised);
}

.folder-card .icon-tile {
  margin-bottom: var(--space-8);
}

.folder-card-name {
  font-size: var(--text-md);
  overflow-wrap: anywhere;
}

.folder-card-link {
  color: var(--color-ink);
  text-decoration: none;
}

.folder-card-link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
}

.folder-card-meta {
  color: var(--color-muted);
  font-size: var(--text-xs);
}

.folder-card-actions {
  position: absolute;
  top: var(--space-12);
  right: var(--space-12);
  z-index: 1;
  display: flex;
  gap: var(--space-4);
}

/* The focus ring goes round the whole card, not just the name. */
@supports selector(:has(*)) {
  .folder-card-link:focus-visible {
    outline: none;
  }

  .folder-card:has(.folder-card-link:focus-visible) {
    outline: var(--focus-outline);
    outline-offset: var(--focus-offset);
  }
}

/* Files on the folder page: the file list, uploads in progress, and the section as a drop area */

.files {
  border-radius: var(--radius-lg);
  transition:
    background-color var(--duration-fast) var(--ease-out),
    outline-color var(--duration-fast) var(--ease-out);
}

/* While files are dragged over the section */
.files.is-drop-target {
  outline: var(--drop-outline);
  outline-offset: var(--space-8);
  background-color: var(--color-primary-soft);
}

.file-list,
.upload-list {
  display: grid;
  gap: var(--space-8);
  padding: 0;
  list-style: none;
}

.upload-list:empty {
  display: none;
}

.file-row,
.upload-row,
.trash-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4) var(--space-12);
  padding: var(--space-12) var(--space-16);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

/* The trash: a row for each deleted file or folder, with Restore and Delete forever. Nothing in it
   can be opened, so its name is plain text. On phones the two buttons take a line of their own under
   the details, since their names are spelled out. From 640px they sit at the end of the row. */
.trash-row {
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "tile details"
    "actions actions";
  row-gap: var(--space-12);
}

.trash-row > .icon-tile {
  grid-area: tile;
}

.trash-row > .file-details {
  grid-area: details;
}

.trash-actions {
  grid-area: actions;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

@media (min-width: 640px) {
  .trash-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "tile details actions";
  }
}

/* On phones the thumbnail and the name share the first line, and the status badge and the file's
   buttons the whole width of a line under them, which the longest status needs beside three buttons.
   From 640px everything shares one line. */
.file-row {
  grid-template-areas:
    "tile details details"
    "badge badge actions";
}

.file-row > .file-thumb {
  grid-area: tile;
  width: var(--file-thumb-width);
  border: var(--border);
  border-radius: var(--radius-sm);
}

.file-row > .file-details {
  grid-area: details;
}

.file-row > .badge {
  grid-area: badge;
  justify-self: start;
}

.file-row > .file-actions {
  grid-area: actions;
  justify-self: end;
}

@media (min-width: 640px) {
  .file-row {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "tile details badge actions";
  }
}

/* "v2" after a file's name, in its folder and on a share link's review screen. */
.version-badge {
  flex-shrink: 0;
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

/* A file's thumbnail: the top of the design, in a frame of one shape whatever the design's own. A
   design wider than the frame sits in the middle of it, whole. A file with no thumbnail shows the
   icon for its type there instead. */
.file-thumb {
  display: grid;
  place-items: center;
  aspect-ratio: var(--thumb-shape);
  overflow: hidden;
  background-color: var(--color-bg);
}

.file-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The file toolbar: sort, the two filters, and the grid or list choice. On phones the names of the
   three lists are left to screen readers, since each list's first choice says what it is for, and
   the controls sit two to a line. From 640px they share one line, with their names showing. */
.file-toolbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-8);
}

.toolbar-field {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.toolbar-field > span {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

.input-sm {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-8);
  font-size: var(--text-sm);
}

.view-toggle {
  display: flex;
  gap: var(--space-4);
  justify-self: end;
  /* After the sort list on phones, so the filters share the line below. */
  grid-row: 1;
  grid-column: 2;
}

.view-toggle .btn-icon[aria-pressed="true"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

@media (min-width: 640px) {
  .file-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-8) var(--space-16);
  }

  .toolbar-field > span {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip: auto;
  }

  .input-sm {
    width: auto;
  }

  .view-toggle {
    margin-left: auto;
  }
}

/* File cards. Two to a line on a phone, more as the page widens. The whole card opens the file
   through its name's link, stretched over the card; the card's own buttons sit above that link. */
.file-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--file-card-min)), 1fr));
  gap: var(--space-12);
  padding: 0;
  list-style: none;
}

.file-card {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-width: 0;
  /* The thumbnail reaches the card's edges, so the card's own padding makes way for its parts'. */
  padding: 0;
  overflow: hidden;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.file-card:hover {
  border-color: var(--color-primary-light);
  box-shadow: var(--shadow-raised);
}

.file-card > .file-thumb {
  border-bottom: var(--border);
}

.file-card-body {
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--space-8);
  min-width: 0;
  padding: var(--space-12) var(--space-8) var(--space-8);
}

/* A long name takes two lines at most, so the cards in a row stay much the same height. */
.file-card .file-name {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* The star: a person's own mark on a file. An outline until it is pressed, then filled in the warm
   accent, the way stars are everywhere. On a card it sits on a corner of the thumbnail, on a white
   disc so it shows on any design. */
.star-button[aria-pressed="true"] {
  color: var(--color-warning);
}

.star-button[aria-pressed="true"] .lucide {
  fill: currentColor;
}

.file-card > .star-button {
  position: absolute;
  top: var(--space-8);
  right: var(--space-8);
  z-index: 1;
  background-color: var(--color-surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-card);
}

.file-card > .star-button:hover {
  background-color: var(--color-surface);
  box-shadow: var(--shadow-raised);
}

/* Beside the file's name on the review screen, the size of the version button under it. */
.star-chip {
  position: relative;
  display: inline-grid;
  flex-shrink: 0;
  place-items: center;
  width: var(--space-24);
  height: var(--space-24);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.star-chip:hover {
  background-color: var(--color-bg);
}

.star-chip .lucide,
.star-chip i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

/* It is small, so the area that takes a tap reaches a little around it. */
.star-chip::after {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--space-8));
}

/* The tabs on the Status page: a status each, with the number of files in it. */
.status-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

.status-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: var(--control-height-sm);
  /* Narrow enough at the sides that two of the three fit on a line of a 360px phone. */
  padding: 0 var(--space-8);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-pill);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

.status-tab:hover {
  border-color: var(--color-primary-light);
}

.status-tab[aria-pressed="true"] {
  background-color: var(--color-primary-soft);
  border-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.status-tab-count {
  min-width: var(--space-16);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.status-tab[aria-pressed="true"] .status-tab-count {
  color: inherit;
}

/* A card's status and version, side by side while there is room. */
.file-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-8);
}

/* The link is stretched over the card, where its own outline would be cut off, so the card shows it. */
.file-card:has(.file-link:focus-visible) {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}

/* The icon stays beside the counts; on a narrow card it is the counts that wrap. */
.file-comments {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  color: var(--color-muted);
  font-size: var(--text-xs);
}

.file-comments.has-open {
  color: var(--color-text);
  font-weight: var(--weight-semibold);
}

.file-comments .lucide,
.file-comments i[data-lucide] {
  flex-shrink: 0;
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.file-card > .file-actions {
  position: relative;
  z-index: 1;
  padding: 0 var(--space-4) var(--space-4);
}

@media (min-width: 640px) {
  .file-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--file-card-min-wide)), 1fr));
    gap: var(--space-16);
  }

  .file-card-body {
    padding: var(--space-12) var(--space-12) var(--space-8);
  }

  .file-card > .file-actions {
    padding: 0 var(--space-8) var(--space-8);
  }
}

.file-details,
.upload-details {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.file-name {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.file-meta,
.upload-status {
  color: var(--color-muted);
  font-size: var(--text-xs);
}

/* Settings: a card for each thing there is to set, in one readable column. */
.settings {
  display: grid;
  gap: var(--space-24);
  max-width: var(--text-width);
}

.settings-card {
  display: grid;
  gap: var(--space-16);
}

.settings-card h2 {
  margin-bottom: var(--space-4);
  font-size: var(--text-lg);
}

.settings-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

/* A switch: a setting that is on or off and takes effect at once. The whole row is its label, so a
   tap anywhere on it works. Which side the handle is on says the setting, not the colour alone. */
.switch-list {
  display: grid;
}

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
  min-height: var(--control-height);
  padding-block: var(--space-12);
  border-top: var(--border);
  cursor: pointer;
}

.switch-text {
  display: grid;
  gap: var(--space-4);
}

.switch-label {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
}

.switch {
  appearance: none;
  position: relative;
  flex: none;
  width: var(--switch-width);
  height: var(--switch-height);
  margin: 0;
  border-radius: var(--radius-pill);
  background-color: var(--color-muted);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.switch::before {
  content: '';
  position: absolute;
  top: calc((var(--switch-height) - var(--switch-thumb)) / 2);
  left: calc((var(--switch-height) - var(--switch-thumb)) / 2);
  width: var(--switch-thumb);
  height: var(--switch-thumb);
  border-radius: var(--radius-pill);
  background-color: var(--color-surface);
  transition: transform var(--duration-fast) var(--ease-out);
}

.switch:checked {
  background-color: var(--color-primary);
}

.switch:checked::before {
  transform: translateX(calc(var(--switch-width) - var(--switch-height)));
}

.switch:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-offset);
}

/* While a change is being saved. */
.switch:disabled {
  opacity: 0.5;
  cursor: progress;
}

/* The logo as it is now, or a line saying there is none, on the page background a share link has. */
.logo-preview {
  display: grid;
  place-items: center;
  min-height: var(--logo-preview-height);
  padding: var(--space-16);
  background-color: var(--color-bg);
  border: var(--border);
  border-radius: var(--radius-md);
}

.logo-preview img {
  max-width: 100%;
  max-height: var(--logo-preview-image);
  object-fit: contain;
}

.logo-preview p {
  color: var(--color-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* The colour picker and, beside it, the same colour as text to type or paste. */
.colour-row {
  display: flex;
  gap: var(--space-8);
}

.colour-swatch {
  flex: none;
  width: var(--control-height);
  height: var(--control-height);
  padding: var(--space-4);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* A sample of the colour being chosen: the page sets the primary colour tokens on this box, so the
   button, badge and link in it are the real components in that colour. */
.brand-preview {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-12);
  padding: var(--space-16);
  background-color: var(--color-bg);
  border: var(--border);
  border-radius: var(--radius-md);
}

.brand-preview-link {
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
  text-decoration: underline;
}

/* Up to four buttons: the star, then the three that change the file. On a phone they sit side by
   side with no gap, so the longest status still fits on the line beside them. */
.file-actions {
  display: flex;
}

@media (min-width: 640px) {
  .file-actions {
    gap: var(--space-4);
  }
}

.upload-row.is-failed {
  border-color: var(--color-danger-soft);
}

.upload-row.is-failed .upload-status {
  color: var(--color-danger-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.upload-retry {
  justify-self: start;
  margin-top: var(--space-4);
}

/* Progress bar */

.progress {
  height: var(--space-8);
  overflow: hidden;
  background-color: var(--color-primary-soft);
  border-radius: var(--radius-pill);
}

.progress-bar {
  display: block;
  width: 0;
  height: 100%;
  background-color: var(--color-primary);
  border-radius: inherit;
  transition: width var(--duration-base) var(--ease-out);
}

/* A file row opens the file through its name's link, stretched over the row; the row's own buttons
   sit above that link. */

.file-row {
  position: relative;
  transition: border-color var(--duration-fast) var(--ease-out);
}

.file-row:hover {
  border-color: var(--color-primary-light);
}

.file-link {
  color: inherit;
  text-decoration: none;
}

.file-link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
}

.file-row > .file-actions {
  position: relative;
  z-index: 1;
}

@supports selector(:has(*)) {
  .file-link:focus-visible {
    outline: none;
  }

  .file-row:has(.file-link:focus-visible) {
    outline: var(--focus-outline);
    outline-offset: var(--focus-offset);
  }
}

/* File viewer and review screen: a full-screen page. The top bar leads back to the folder and holds
   the status, Download and zoom. Below it the design scrolls in the centre, with page thumbnails on
   the left for PDFs from 768px, and the comment panel on the right from 1024px. Narrower screens
   get the panel as a sheet along the bottom. */

.viewer-screen {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

/* On phones the status and zoom get their own row under the name; from 768px everything shares one row.
   Guests on a share link have no back link or Share button. */
.viewer-bar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto auto;
  grid-template-areas:
    "back heading comment share download"
    "tools tools tools tools tools";
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-8) var(--space-16);
  background-color: var(--color-surface);
  border-bottom: var(--border);
}

.viewer-bar.is-guest {
  grid-template-columns: minmax(0, 1fr) auto auto;
  grid-template-areas:
    "heading comment download"
    "tools tools tools";
}

.viewer-back {
  grid-area: back;
}

.viewer-heading {
  grid-area: heading;
  min-width: 0;
}

.viewer-name,
.viewer-workspace {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On a share link the workspace's logo, when it has one, comes before its name. On a phone it sits
   small on that name's own line, so the file's name under it keeps the whole width. From 640px it
   stands to the left of all three lines, larger. Either way it is drawn whole, whatever its shape. */
.viewer-bar.is-guest .viewer-heading {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--space-8);
}

/* On a phone the three lines of text are laid out by the heading's own grid, beside the logo. */
.viewer-heading-text {
  display: contents;
}

.viewer-logo {
  grid-area: 1 / 1;
  width: auto;
  max-width: var(--viewer-logo-width);
  height: var(--viewer-logo-height);
  object-fit: contain;
}

.viewer-bar.is-guest .viewer-workspace {
  grid-area: 1 / 2;
}

/* The file's name and details take the whole width, and with no logo so does the workspace's name. */
.viewer-logo[hidden] + .viewer-heading-text .viewer-workspace,
.viewer-bar.is-guest .viewer-name,
.viewer-bar.is-guest .viewer-subline {
  grid-column: 1 / -1;
}

@media (min-width: 640px) {
  .viewer-bar.is-guest .viewer-heading {
    display: flex;
    gap: var(--space-12);
  }

  .viewer-heading-text {
    display: block;
    min-width: 0;
  }

  .viewer-logo {
    flex: none;
    max-width: var(--viewer-logo-width-wide);
    height: var(--viewer-logo-height-wide);
  }
}

.viewer-name {
  font-size: var(--text-md);
}

/* The name and, right after it, the star. A long name is cut short; the star always shows. */
.viewer-title {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
}

.viewer-title .viewer-name {
  min-width: 0;
}

.viewer-meta {
  min-width: 0;
  overflow: hidden;
  color: var(--color-muted);
  font-size: var(--text-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Under the name: which version is on screen, then the file's type and size. They share one line, so
   the top bar stays the same height; on a narrow phone the details are cut short, not the version. */
.viewer-subline {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
}

/* The version on screen, as a small button that opens the list of versions. An earlier version is
   marked in the warning colours, so it is not mistaken for the one being reviewed now. */
.version-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--space-24);
  padding: 0 var(--space-8);
  border: 0;
  border-radius: var(--radius-xs);
  background-color: var(--color-bg);
  color: var(--color-ink);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

/* The button is small, so the area that takes a tap reaches a little above and below it. */
.version-button::after {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--space-8)) 0;
}

.version-button:hover,
.version-button[aria-expanded="true"] {
  background-color: var(--color-primary-soft);
}

.version-button[data-earlier] {
  background-color: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.version-button .lucide,
.version-button i[data-lucide] {
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
}

.version-panel {
  right: auto;
  left: 0;
  width: var(--version-menu-width);
  max-width: calc(100vw - var(--space-32));
}

.version-list {
  display: grid;
  gap: var(--space-4);
  max-height: var(--version-list-height);
  padding: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

.version-item {
  padding-block: var(--space-8);
}

.version-item-text {
  display: grid;
  gap: var(--space-4);
  min-width: 0;
}

.version-item-title {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
}

.version-item-meta {
  color: var(--color-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  overflow-wrap: anywhere;
}

/* The tick marks the version on screen. */
.version-item .lucide,
.version-item i[data-lucide] {
  flex-shrink: 0;
  margin-left: auto;
  color: var(--color-primary);
  visibility: hidden;
}

.version-item[aria-current="true"] .lucide {
  visibility: visible;
}

/* Upload new version, set apart from the list above it by a line. */
.version-upload {
  position: relative;
  margin-top: var(--space-8);
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

.version-upload::before {
  content: '';
  position: absolute;
  top: calc(-1 * var(--space-4) - 1px);
  right: 0;
  left: 0;
  border-top: var(--border);
}

.version-upload:hover {
  color: var(--color-primary-hover);
}

.viewer-tools {
  grid-area: tools;
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

/* Comment, Share and Download: icon only below 1024px; the labels join them once the top bar has room. */
.comment-button {
  grid-area: comment;
}

.share-button {
  grid-area: share;
}

.download-button {
  grid-area: download;
}

.comment-button,
.share-button,
.download-button {
  width: var(--control-height-sm);
  padding: 0;
}

.comment-button-label,
.share-label,
.download-label {
  display: none;
}

/* Comment mode off: a secondary button (white, border, ink icon). On: the filled primary button, which
   people read as switched on. */
.comment-button[aria-pressed="true"] {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}

.comment-button[aria-pressed="true"]:hover {
  background-color: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color: var(--color-on-primary);
}

/* The status badge doubles as the button that changes it. */
.status-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--control-height-sm);
  padding: 0 var(--space-12);
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
}

.status-button .lucide,
.status-button i[data-lucide] {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.status-button[data-status="in_review"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.status-button[data-status="approved"] {
  background-color: var(--color-success-soft);
  color: var(--color-success-text);
}

.status-button[data-status="changes_requested"] {
  background-color: var(--color-warning-soft);
  color: var(--color-warning-text);
}

.status-panel {
  right: auto;
  left: 0;
}

.status-panel[aria-busy="true"] {
  opacity: 0.6;
  pointer-events: none;
}

.status-dot {
  flex-shrink: 0;
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-pill);
}

.status-dot[data-status="in_review"] {
  background-color: var(--color-primary);
}

.status-dot[data-status="approved"] {
  background-color: var(--color-success);
}

.status-dot[data-status="changes_requested"] {
  background-color: var(--color-warning);
}

/* The tick marks the current status. */
.status-panel .menu-item .lucide,
.status-panel .menu-item i[data-lucide] {
  margin-left: auto;
  visibility: hidden;
}

.status-panel .menu-item[aria-pressed="true"] {
  color: var(--color-ink);
}

.status-panel .menu-item[aria-pressed="true"] .lucide {
  visibility: visible;
}

/* "Page 3 of 40", floating at the foot of the design. */
.page-indicator {
  grid-area: viewer;
  align-self: end;
  justify-self: center;
  z-index: 2;
  margin-bottom: var(--space-16);
  padding: var(--space-4) var(--space-12);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  color: var(--color-ink);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}

.zoom-controls {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-left: auto;
}

/* Hidden on the narrowest phones, where the row only has room for the buttons. Zoom out, zoom in
   and Fit stay, so zooming never needs a pinch. */
.zoom-level {
  display: none;
  min-width: var(--space-48);
  color: var(--color-ink);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.viewer-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  grid-template-areas:
    "viewer"
    "panel";
  min-height: 0;
}

.thumbnails {
  grid-area: thumbs;
  display: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-16) var(--space-12);
  background-color: var(--color-surface);
  border-right: var(--border);
}

.thumbnail-list {
  display: grid;
  gap: var(--space-8);
  padding: 0;
  list-style: none;
}

.thumbnail {
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-8);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.thumbnail:hover {
  background-color: var(--color-bg);
}

.thumbnail[aria-current="page"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.thumbnail-frame {
  display: block;
  width: 100%;
  background-color: var(--color-surface);
  border: var(--border);
  box-shadow: var(--shadow-card);
}

.thumbnail[aria-current="page"] .thumbnail-frame {
  border-color: var(--color-primary);
}

.thumbnail-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.thumbnail-number {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.viewer {
  grid-area: viewer;
  overflow: auto;
  overscroll-behavior: contain;
  /* Room for the scrollbar from the start, so it appearing does not change the width a page fits. */
  scrollbar-gutter: stable;
  /* Pinching zooms the design (page-stack.js), not the whole page, so the browser only scrolls here. */
  touch-action: pan-x pan-y;
}

.viewer:focus-visible {
  outline-offset: calc(var(--focus-offset) * -1);
}

/* As wide as the widest page, or the viewer if that is wider, so a zoomed page scrolls from its left edge. */
.viewer-content {
  position: relative;
  width: max-content;
  min-width: 100%;
  padding: var(--space-12);
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

.viewer.is-panning .viewer-content {
  cursor: grabbing;
}

.doc-page {
  position: relative;
  margin: 0 auto var(--space-16);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-card);
}

.doc-page:last-child {
  margin-bottom: 0;
}

.doc-canvas,
.doc-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.doc-detail {
  position: absolute;
}

/* The password form on a protected share link: a centred card like the other states, with its field
   and button the card's full width. */
.unlock-card {
  display: grid;
  justify-items: center;
  gap: var(--space-12);
  width: min(100%, var(--form-width));
  text-align: center;
}

.unlock-card h2 {
  font-size: var(--text-lg);
}

.unlock-card .field,
.unlock-card .btn {
  justify-self: stretch;
}

.unlock-card .field {
  margin-top: var(--space-8);
  text-align: left;
}

/* Loading, error and missing states span the panel's column too, which stays empty until the file shows. */
.viewer-state {
  grid-area: viewer;
  grid-column: 1 / -1;
  display: grid;
  place-items: center;
  align-content: center;
  padding: var(--space-24) var(--space-16);
}

/* The comment panel. As a bottom sheet only its header shows until it is opened; the header's
   button is stretched over the whole header, so tapping anywhere on it opens or closes the sheet. */
.review-panel {
  grid-area: panel;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  background-color: var(--color-surface);
  border-top: var(--border);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}

.review-panel-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  padding: var(--space-8) var(--space-16);
}

.review-panel-header h2 {
  font-size: var(--text-md);
}

.review-panel-toggle::after {
  content: '';
  position: absolute;
  inset: 0;
}

.review-panel-toggle .lucide {
  transition: transform var(--duration-fast) var(--ease-out);
}

.review-panel.is-open .review-panel-toggle .lucide {
  transform: rotate(180deg);
}

.review-panel-body {
  display: none;
  /* A set height, not a ceiling: the sheet's header and filter tabs stay in the same place whichever
     filter is chosen, so the next tap lands where it was aimed, and an empty list keeps its message
     inside the open sheet. */
  height: var(--review-sheet-height);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-top: var(--border);
}

.review-panel.is-open .review-panel-body {
  display: block;
}

/* Comments on the design: numbered pins, marked areas, the form for a new comment, and comment mode.
   Each reviewer's colour comes in as pin-color-1 to pin-color-6, with a number colour that reads on it. */

.pin-color-1 { --pin: var(--color-pin-1); --pin-number: var(--color-on-primary); }
.pin-color-2 { --pin: var(--color-pin-2); --pin-number: var(--color-ink); }
.pin-color-3 { --pin: var(--color-pin-3); --pin-number: var(--color-ink); }
.pin-color-4 { --pin: var(--color-pin-4); --pin-number: var(--color-on-primary); }
.pin-color-5 { --pin: var(--color-pin-5); --pin-number: var(--color-ink); }
.pin-color-6 { --pin: var(--color-pin-6); --pin-number: var(--color-ink); }

/* Over the page's drawings, and out of the way of clicks except on the pins themselves. */
.pin-layer {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* Centred on its spot, so the spot is the same at every zoom. */
.pin {
  position: absolute;
  display: grid;
  place-items: center;
  width: var(--pin-size);
  height: var(--pin-size);
  padding: 0;
  background-color: var(--pin);
  border: 2px solid var(--color-surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  color: var(--pin-number);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: pointer;
  transition:
    transform var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.pin.is-active {
  z-index: 1;
  box-shadow: 0 0 0 var(--space-4) var(--pin), var(--shadow-raised);
  transform: translate(-50%, -50%) scale(1.15);
}

/* Resolved: an outline in the reviewer's colour, the number in body text. */
.pin.is-resolved {
  background-color: var(--color-surface);
  border-color: var(--pin);
  color: var(--color-text);
}

/* The same pin, as the number at the start of a comment in the list. */
.pin-static {
  position: static;
  flex-shrink: 0;
  box-shadow: none;
  transform: none;
}

.pin-static.is-active {
  box-shadow: none;
  transform: none;
}

.pin-box {
  position: absolute;
  background-color: color-mix(in srgb, var(--pin) 12%, transparent);
  border: 2px solid var(--pin);
  border-radius: var(--radius-xs);
  pointer-events: none;
}

.pin-box.is-active {
  background-color: color-mix(in srgb, var(--pin) 22%, transparent);
}

.pin-box.is-resolved {
  background-color: transparent;
  border-style: dashed;
}

/* Where a new comment will go, while its form is open. */
.pin-draft {
  --pin: var(--color-primary);
  pointer-events: none;
}

.pin.pin-draft {
  background-color: var(--color-surface);
  border-color: var(--color-primary);
}

.pin-box.pin-draft {
  border-style: dashed;
}

/* The form for a new comment, beside its spot. The shifts flip it left or up near the page's right
   or bottom edge, or centre it on narrow screens. */
.comment-popover {
  --shift-x: 0%;
  --shift-y: 0%;
  position: absolute;
  z-index: 3;
  width: var(--popover-width);
  max-width: calc(100vw - var(--space-32));
  margin: var(--space-12) 0 0 var(--space-12);
  padding: var(--space-12);
  background-color: var(--color-surface);
  border: var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
  transform: translate(var(--shift-x), var(--shift-y));
  cursor: auto;
  user-select: text;
  -webkit-user-select: text;
}

.comment-popover.opens-left {
  --shift-x: -100%;
  margin-left: calc(var(--space-12) * -1);
}

.comment-popover.opens-up {
  --shift-y: -100%;
  margin-top: calc(var(--space-12) * -1);
}

.comment-popover.is-centred {
  --shift-x: -50%;
  margin-left: 0;
}

/* Comment mode: a crosshair, and one finger draws instead of scrolling. */
.viewer.is-commenting {
  touch-action: none;
}

.viewer.is-commenting .viewer-content {
  cursor: crosshair;
}

.comment-hint {
  grid-area: viewer;
  align-self: start;
  justify-self: center;
  z-index: 2;
  max-width: calc(100% - var(--space-32));
  margin-top: var(--space-16);
  padding: var(--space-8) var(--space-16);
  background-color: var(--color-ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  color: var(--color-on-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-align: center;
  pointer-events: none;
}

/* Over the top of an earlier version's design: which version it is, that it can only be looked at,
   and the way to the latest. */
.version-notice {
  grid-area: viewer;
  align-self: start;
  justify-self: center;
  z-index: 2;
  max-width: calc(100% - var(--space-32));
  margin-top: var(--space-16);
  padding: var(--space-8) var(--space-16);
  background-color: var(--color-warning-soft);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
  color: var(--color-warning-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-align: center;
}

.version-notice a {
  color: var(--color-ink);
  text-decoration: underline;
}

/* The comment list in the review panel */

.comment-filters {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: var(--space-4);
  padding: var(--space-12) var(--space-16);
  background-color: var(--color-surface);
  border-bottom: var(--border);
}

.comment-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--control-height-sm);
  padding: 0 var(--space-12);
  background: none;
  border: 0;
  border-radius: var(--radius-pill);
  color: var(--color-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.comment-filter:hover {
  background-color: var(--color-bg);
}

.comment-filter[aria-pressed="true"] {
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
}

.comment-filter-count {
  font-variant-numeric: tabular-nums;
}

.comment-list {
  display: grid;
  padding: 0;
  list-style: none;
}

.comment-thread {
  display: grid;
  gap: var(--space-8);
  padding: var(--space-16);
  border-bottom: var(--border);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.comment-thread:hover,
.comment-thread.is-active {
  background-color: var(--color-bg);
}

/* The chosen comment gets a bar down its left edge. */
.comment-thread.is-active {
  box-shadow: inset var(--space-4) 0 0 var(--color-primary);
}

.comment-thread.is-resolved > .comment-text {
  color: var(--color-muted);
}

.comment-head {
  display: flex;
  align-items: center;
  gap: var(--space-12);
}

.comment-who {
  flex: 1;
  min-width: 0;
}

.comment-author {
  color: var(--color-ink);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.comment-meta {
  color: var(--color-muted);
  font-size: var(--text-xs);
}

.comment-text {
  color: var(--color-text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.comment-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-16);
}

.comment-action {
  font-size: var(--text-xs);
}

.comment-replies {
  display: grid;
  gap: var(--space-12);
  margin-left: calc(var(--pin-size) / 2);
  padding: 0 0 0 var(--space-16);
  border-left: var(--border);
  list-style: none;
}

.comment-replies:empty {
  display: none;
}

.comment-reply {
  display: grid;
  gap: var(--space-4);
}

.comment-dot {
  flex-shrink: 0;
  width: var(--space-12);
  height: var(--space-12);
  background-color: var(--pin);
  border-radius: var(--radius-pill);
}

.comment-resolve {
  flex-shrink: 0;
}

.comment-form {
  display: grid;
  gap: var(--space-8);
  cursor: auto;
}

.comment-input {
  min-height: calc(var(--control-height) * 2);
  resize: vertical;
}

.comment-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-8);
}

.comment-empty {
  padding: var(--space-32) var(--space-16);
}

/* How many comments a PDF page has, on its thumbnail. */
.thumbnail-frame {
  position: relative;
}

.thumbnail-count {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  z-index: 1;
  padding: 0 var(--space-8);
  background-color: var(--color-primary);
  border-radius: var(--radius-pill);
  color: var(--color-on-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: var(--leading-normal);
}

@media (min-width: 480px) {
  .zoom-level {
    display: inline-block;
  }

  .viewer-tools {
    gap: var(--space-12);
  }
}

@media (min-width: 768px) {
  .viewer-bar {
    grid-template-columns: auto minmax(0, 1fr) auto auto auto auto;
    grid-template-areas: "back heading tools comment share download";
    column-gap: var(--space-12);
    padding: var(--space-12) var(--space-24);
  }

  .viewer-bar.is-guest {
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    grid-template-areas: "heading tools comment download";
  }

  .viewer-main.has-thumbnails {
    grid-template-columns: var(--thumbnail-strip-width) minmax(0, 1fr);
    grid-template-areas:
      "thumbs viewer"
      "panel panel";
  }

  .viewer-main.has-thumbnails .thumbnails {
    display: block;
  }

  .viewer-content {
    padding: var(--space-24);
  }
}

@media (min-width: 1024px) {
  .viewer-tools {
    gap: var(--space-24);
  }

  .comment-button,
  .share-button,
  .download-button {
    width: auto;
    padding: 0 var(--space-16);
  }

  .comment-button-label,
  .share-label,
  .download-label {
    display: inline;
  }

  .viewer-main {
    grid-template-columns: minmax(0, 1fr) var(--review-panel-width);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "viewer panel";
  }

  .viewer-main.has-thumbnails {
    grid-template-columns: var(--thumbnail-strip-width) minmax(0, 1fr) var(--review-panel-width);
    grid-template-areas: "thumbs viewer panel";
  }

  .review-panel {
    border-top: 0;
    border-left: var(--border);
    border-radius: 0;
  }

  .review-panel-header {
    padding: var(--space-16) var(--space-24);
  }

  .review-panel-toggle {
    display: none;
  }

  .review-panel-body {
    display: block;
    height: auto;
  }
}

@media (min-width: 640px) {
  .app-user-name {
    display: inline;
  }
}

@media (min-width: 960px) {
  .app-shell {
    display: grid;
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    padding-bottom: 0;
  }

  .app-sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    padding: var(--space-24) var(--space-16);
    border-top: 0;
    border-right: var(--border);
  }

  .app-sidebar .brand {
    display: inline-flex;
    padding-inline: var(--space-8);
  }

  .app-sidebar .nav-list {
    display: grid;
    justify-content: stretch;
    gap: var(--space-4);
  }

  .app-sidebar .nav-list > li {
    max-width: none;
  }

  /* Beside the content there is room for every link, so none is tucked away and there is no More. */
  .app-sidebar .nav-list[data-crowded] > [data-nav-extra] {
    display: block;
  }

  .app-sidebar .nav-list[data-crowded] > .nav-more {
    display: none;
  }

  .app-sidebar .nav-item {
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-12);
    height: auto;
    padding: 0 var(--space-12);
    font-size: var(--text-sm);
    line-height: inherit;
    text-align: left;
  }

  .app-topbar {
    padding: 0 var(--space-32);
  }

  .app-topbar-mark {
    display: none;
  }

  .app-content {
    padding: var(--space-32);
  }
}

/* Animations. The reduced-motion rule in base.css shortens them to nothing. */

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes fade-in {
  from { opacity: 0; }
}

@keyframes modal-in {
  from {
    opacity: 0;
    transform: translateY(var(--space-8)) scale(0.98);
  }
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(var(--space-8));
  }
}
