/* ==========================================================================
   protectedtext.css — the protected-text vaults (securetext/templates/texts/).

   Eight screens, and between them they carried 511 lines of `<style>`: 90 in
   view_text.html alone, defining a `.cs-modal-backdrop` at
   `z-index: 9999999 !important` because a hand-rolled overlay was the only kind
   that worked inside `.main-section`. That is no longer true — the shell fixed
   its own stacking context on 2026-09-01 (accounts/base.html rules 3b and 4),
   so these screens use the house dialog instead: Bootstrap's `.modal`, with
   only the inner panel styled here. See `.pt-modal` far below.

   Same contract as voicetyping.css beside it, and as shorturl.css, youtube.css
   and cropcut.css elsewhere:

     * every selector is prefixed .pt-scope, matching the wrapper <div> each
       template opens with. accounts/base.html renders {% block extra_css %}
       BEFORE the theme, MDB, the Bootstrap 4.3.1 CDN sheet and custom.css, so
       this file can only ever win on specificity, never on order;
     * no :root block — main.models.ColorPreset injects the palette after every
       stylesheet, and a :root here would override the admin's chosen colours;
     * --color-primary and its siblings are never redeclared: defining one in
       terms of itself is a var() cycle that blanks it for the whole subtree.
       They are read inline with a literal fallback instead;
     * only Montserrat and Open Sans 400, 700 and 800 are loaded, so there is no
       font-weight 500 or 600 below. The old sheets used 600 eleven times;
     * layout is CSS grid, never Bootstrap's row/col;
     * nothing here is positioned by z-index. `.modal` gets its layer from the
       shell, which is the only place that can grant one.

   Tokens are voicetyping.css's verbatim, which are youtube.css's verbatim. The
   voice tool and the vaults are the two halves of one app and share a sidebar
   row; crossing between them should not cross a seam.
   ========================================================================== */
.pt-scope {
  --pt-surface: #ffffff;
  --pt-surface-muted: #fbfbfc;
  --pt-surface-sunken: #f4f5f8;
  --pt-border: #eceef4;
  --pt-border-strong: #e4e6ee;

  --pt-text: var(--color-default, #2f313a);
  --pt-text-soft: #6c707a;
  --pt-text-muted: #9097a1;

  --pt-brand: var(--color-primary, #c33332);
  --pt-brand-hover: var(--color-primary-hover, #a82b2a);
  --pt-brand-bg: var(--color-primary-light, #fbf2f2);
  --pt-good: #1e8e3e;
  --pt-good-bg: #eaf5ee;
  --pt-good-border: #bfe0c9;
  --pt-warn: #9a6a00;
  --pt-warn-bg: #fdf6e6;
  --pt-warn-border: #ecd9a6;
  --pt-danger: #c33332;
  --pt-danger-bg: #fdf1f1;
  --pt-danger-border: #f0cccc;

  --pt-font-display: "Montserrat", "Noto Sans Bengali", -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  --pt-font-body: "Open Sans", "Noto Sans Bengali", -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  --pt-font-mono: "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo",
    monospace;

  --pt-radius-sm: 4px;
  --pt-radius-md: 5px;

  padding: 4px 0 32px;
  font-family: var(--pt-font-body);
  color: var(--pt-text);
}

/* mdb.min.css uppercases and drop-shadows every button on the page and
   Bootstrap 4.3.1, loading after it, resets neither. These screens use .pt-btn
   rather than .btn for that reason; the reset stays as insurance for anything
   the shell renders inside the wrapper — including CKEditor's own toolbar. */
.pt-scope .btn {
  text-transform: none;
  box-shadow: none;
}
/* --------------------------------------------------------------------------
   Page head. 21px Montserrat in the body ink, the same metric as .vt-title on
   the voice screen and .yt-title on the downloader.

   .pt-title is an <h1>, and every heading rule in this file carries !important
   on all seven type properties. That is not belt-and-braces: TWO global rules
   govern headings here, and the one that decides is
   cs-foodbakery-plugin.css:2044 — `h1, h1 a { font: 700 Normal 30px/36px
   "Montserrat" !important; letter-spacing: 1px !important; text-transform:
   capitalize !important; color: #000000 !important }`, with the same shape for
   h2 (24/30) down to h6 (14px). An author !important declaration beats a
   non-important one at any specificity, so a scoped 13px sentence-case heading
   prints as a 24px Title Cased black one unless it fights back. A 0-2-0 rule
   with !important outranks the element-level 0-0-1 one, so the override stays
   contained. Any phone override of those properties then needs !important too,
   or it loses to the desktop rule instead of to the theme.

   Paragraphs need the same treatment for colour alone: accounts/base.html
   writes `.main-section p { ... color: #515151 !important }` into an inline
   <style> AFTER extra_css, so every <p> here restates its own colour.

   The lede is capped at 128ch — a stop against a 2560px monitor handing it a
   300-character line, not a reading measure. A page-head lede owns the full
   row; 62-68ch in a ~1000px column reads as a broken layout and has been
   reported as one three times.
   -------------------------------------------------------------------------- */
.pt-scope .pt-head {
  margin-bottom: 18px;
}

.pt-scope .pt-eyebrow {
  margin: 0 0 5px;
  font-family: var(--pt-font-display);
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--pt-text-muted) !important;
}

.pt-scope .pt-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--pt-font-display) !important;
  font-size: 21px !important;
  font-weight: 700 !important;
  line-height: 28px !important;
  letter-spacing: -0.2px !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
}

.pt-scope .pt-title i {
  font-size: 19px;
  color: var(--pt-brand);
}

.pt-scope .pt-lede {
  max-width: 128ch;
  margin: 7px 0 0;
  font-size: 13px;
  line-height: 21px;
  color: var(--pt-text-soft) !important;
}

/* The name of the vault, printed in the head beside the title. Monospaced
   because it is a fragment of a URL and the difference between `my-keys` and
   `my_keys` has to be visible. */
.pt-scope .pt-name {
  font-family: var(--pt-font-mono);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--pt-brand);
  word-break: break-all;
}
/* --------------------------------------------------------------------------
   State pills. A vault is one of three things and a visitor cannot tell which
   from the page alone: locked (a password opened it, so the manage controls are
   there), published (no password at all — readable by anyone, writable by
   nobody through the web), or read-only (a locked vault this session has not
   unlocked). The old screens said none of it; the manage buttons were simply
   present for everyone, which is exactly the hole that was closed in views.py.
   -------------------------------------------------------------------------- */
.pt-scope .pt-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 9px 0 0;
}

.pt-scope .pt-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--pt-border-strong);
  border-radius: 20px;
  background: var(--pt-surface-sunken);
  font-family: var(--pt-font-display);
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.2px;
  color: var(--pt-text-soft);
}

.pt-scope .pt-tag i { font-size: 11px; }

.pt-scope .pt-tag.is-good {
  border-color: var(--pt-good-border);
  background: var(--pt-good-bg);
  color: var(--pt-good);
}

.pt-scope .pt-tag.is-warn {
  border-color: var(--pt-warn-border);
  background: var(--pt-warn-bg);
  color: var(--pt-warn);
}

.pt-scope .pt-tag.is-brand {
  border-color: var(--pt-danger-border);
  background: var(--pt-brand-bg);
  color: var(--pt-brand);
}
/* --------------------------------------------------------------------------
   The polite announcer. Off-screen but not `display: none`, which screen
   readers skip. Everything the reveal panel does silently — a snippet opened, a
   snippet copied, an order saved, an order refused — is written here too, so a
   reader who cannot see the panel change is still told it changed.
   -------------------------------------------------------------------------- */
.pt-scope .pt-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Notices. One shape for four jobs: the result of a press, the "this vault has
   no password" standing warning, a form's error summary, and the reorder
   endpoint's refusal. In flow rather than floating, so a message can never land
   on top of the control it is reporting on — the old view_text.html put its
   feedback in a `position: fixed` toast at z-index 9999999.
   -------------------------------------------------------------------------- */
.pt-scope .pt-alert {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 16px;
  padding: 11px 14px;
  border: 1px solid var(--pt-border-strong);
  border-radius: var(--pt-radius-sm);
  background: var(--pt-surface-sunken);
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-text-soft) !important;
}

.pt-scope .pt-alert i { flex: none; font-size: 14px; line-height: 19px; }
.pt-scope .pt-alert p { margin: 0; color: inherit !important; }
.pt-scope .pt-alert[hidden] { display: none; }

.pt-scope .pt-alert.is-good {
  border-color: var(--pt-good-border);
  background: var(--pt-good-bg);
  color: var(--pt-good) !important;
}
.pt-scope .pt-alert.is-warn {
  border-color: var(--pt-warn-border);
  background: var(--pt-warn-bg);
  color: var(--pt-warn) !important;
}

.pt-scope .pt-alert.is-bad {
  border-color: var(--pt-danger-border);
  background: var(--pt-danger-bg);
  color: var(--pt-danger) !important;
}

/* Holds its height when empty so the panel below does not jump down the page
   the first time a message arrives. */
.pt-scope .pt-status {
  min-height: 41px;
  margin: 0 0 14px;
}

.pt-scope .pt-status .pt-alert { margin: 0; }

/* A form's own errors, listed above its fields. Django renders them as a <ul>
   and the theme gives that bullets and a left indent it does not need here. */
.pt-scope .pt-errors {
  margin: 0;
  padding: 0;
  list-style: none;
}

.pt-scope .pt-errors li {
  margin: 0 0 3px;
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-danger) !important;
}

.pt-scope .pt-errors li:last-child { margin-bottom: 0; }
/* --------------------------------------------------------------------------
   Cards. One panel per job. The screens being replaced had no panel at all on
   four of the eight — the fields sat directly on the dashboard's white
   background under an <hr> — and three of the others drew their own gradient
   header in a green (#00a474) from no palette this site has.
   -------------------------------------------------------------------------- */
.pt-scope .pt-card {
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface);
  overflow: hidden;
}
.pt-scope .pt-card + .pt-card { margin-top: 16px; }

.pt-scope .pt-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--pt-border);
  background: var(--pt-surface-muted);
}

/* An <h2>. !important for the reason given at the top of the file. */
.pt-scope .pt-card__heading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-family: var(--pt-font-display) !important;
  font-size: 13px !important;
  font-weight: 700 !important;
  line-height: 20px !important;
  letter-spacing: 0.2px !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
}

.pt-scope .pt-card__heading i { font-size: 14px; color: var(--pt-brand); }

.pt-scope .pt-card__count {
  font-family: var(--pt-font-body);
  font-size: 12px;
  font-weight: 400;
  color: var(--pt-text-muted);
}

.pt-scope .pt-card__body { padding: 18px; }

.pt-scope .pt-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 13px 18px;
  border-top: 1px solid var(--pt-border);
  background: var(--pt-surface-muted);
}

.pt-scope .pt-card__foot form { margin: 0; }
/* --------------------------------------------------------------------------
   The vault's two columns: the rail of snippet names on the left, the one that
   is open on the right. Below 900px they stack, rail first — which is the
   reading order as well, so nothing needs reordering.

   `min-width: 0` on both tracks is load-bearing. A grid item's default minimum
   is its content, and the reveal panel holds pasted rich text with unbroken
   tokens in it (an API key, a URL); without this the column widens to the
   longest line and pushes the rail off the screen.
   -------------------------------------------------------------------------- */
.pt-scope .pt-columns {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  align-items: start;
  gap: 16px;
}

.pt-scope .pt-columns > * { min-width: 0; }

/* The single-form screens — claim a name, set a password, unlock, confirm a
   deletion. A 1000px-wide card holding one 38px field looks unfinished, so the
   card is capped and centred. Not a reading measure: there is no prose in it. */
.pt-scope .pt-gate {
  max-width: 560px;
  margin: 0 auto;
}

/* --------------------------------------------------------------------------
   Forms. Two columns on a wide card, one on a phone; a field can claim the full
   row with .is-wide. The screens being replaced used `{{ form.as_p }}` inside
   Bootstrap columns, which put a 100%-wide input, its label and its help text
   in a <p> the theme had already styled for prose.
   -------------------------------------------------------------------------- */
.pt-scope .pt-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 16px;
}

.pt-scope .pt-form.is-single { grid-template-columns: minmax(0, 1fr); }
.pt-scope .pt-field.is-wide { grid-column: 1 / -1; }

.pt-scope .pt-label {
  display: block;
  margin: 0 0 5px;
  font-family: var(--pt-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.2px;
  color: var(--pt-text);
}
/* Widgets are addressed by element rather than by class, so forms.py stays free
   of presentation: the only attrs it sets are placeholders, autocomplete and
   maxlength, all of which are behaviour. The theme's own `input` rule is
   style.css:2117 (40px box, 12px, #9097a1) and this is the same shape one point
   larger, because a password typed into a 12px box is hard to check. */
.pt-scope .pt-field input,
.pt-scope .pt-input {
  width: 100%;
  height: 38px;
  padding: 0 11px;
  border: 1px solid var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface);
  font-family: var(--pt-font-body);
  font-size: 13px;
  font-weight: 400;
  line-height: 20px;
  color: var(--pt-text);
}

.pt-scope .pt-field input:focus,
.pt-scope .pt-input:focus {
  outline: none;
  border-color: var(--pt-brand);
  box-shadow: 0 0 0 3px var(--pt-brand-bg);
}

.pt-scope .pt-field input::placeholder,
.pt-scope .pt-input::placeholder { color: var(--pt-text-muted); }

/* Django marks an invalid field's widget for us via .is-invalid nowhere, so the
   red edge is put on the wrapper by the template instead. */
.pt-scope .pt-field.has-error input {
  border-color: var(--pt-danger-border);
  background: var(--pt-danger-bg);
}

.pt-scope .pt-help {
  display: block;
  margin: 5px 0 0;
  font-size: 12px;
  line-height: 18px;
  color: var(--pt-text-muted) !important;
}

.pt-scope .pt-error {
  display: block;
  margin: 5px 0 0;
  font-size: 12px;
  line-height: 18px;
  color: var(--pt-danger) !important;
}
/* --------------------------------------------------------------------------
   The URL name field, shown as the address it will become. Three screens told
   the visitor "your text will be at domain/text/name" in a sentence below the
   box; the box now says it itself, so what is being reserved is visible while
   it is being typed. The prefix is not part of the value — it is a <span>.
   -------------------------------------------------------------------------- */
.pt-scope .pt-affix {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface);
  overflow: hidden;
}

.pt-scope .pt-affix:focus-within {
  border-color: var(--pt-brand);
  box-shadow: 0 0 0 3px var(--pt-brand-bg);
}

.pt-scope .pt-affix__prefix {
  display: flex;
  align-items: center;
  flex: none;
  max-width: 55%;
  padding: 0 4px 0 11px;
  background: var(--pt-surface-sunken);
  border-right: 1px solid var(--pt-border);
  font-family: var(--pt-font-mono);
  font-size: 12px;
  line-height: 36px;
  color: var(--pt-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The input inside loses its own frame — the wrapper is the frame now — and its
   focus ring with it, which is why :focus-within is on the wrapper above. */
.pt-scope .pt-affix input {
  border: 0;
  border-radius: 0;
  background: transparent;
  font-family: var(--pt-font-mono);
}

.pt-scope .pt-affix input:focus {
  box-shadow: none;
  border-color: transparent;
}
/* --------------------------------------------------------------------------
   Show/hide on a password box. Three of the old screens each carried their own
   copy of this, and each one built the button from a markup string — one of
   them appended it to the field's parent on every keystroke. It is markup now,
   and the script only flips a class and an aria-pressed.

   The icon is a Bootstrap Icon, not a Font Awesome one, on purpose: the shell
   loads Font Awesome's **JS** build (accounts/base.html:51), which rewrites
   every `<i class="fa…">` into an `<svg>` with the path baked in, so toggling
   `fa-eye` -> `fa-eye-slash` at runtime changes nothing on screen. `bi-*` is a
   plain webfont, so a class swap is enough.
   -------------------------------------------------------------------------- */
.pt-scope .pt-secret { position: relative; }

.pt-scope .pt-secret input { padding-right: 40px; }

.pt-scope .pt-eye {
  position: absolute;
  top: 1px;
  right: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 0 var(--pt-radius-md) var(--pt-radius-md) 0;
  background: transparent;
  font-size: 14px;
  color: var(--pt-text-muted);
  cursor: pointer;
}

.pt-scope .pt-eye:hover,
.pt-scope .pt-eye:focus {
  background: var(--pt-surface-sunken);
  color: var(--pt-brand);
  outline: none;
}

/* --------------------------------------------------------------------------
   Buttons. .pt-btn, never .btn — see the reset at the top of this file.
   -------------------------------------------------------------------------- */
.pt-scope .pt-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 16px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--pt-border);
}

.pt-scope .pt-actions.is-bare { margin: 0; padding: 0; border-top: 0; }
.pt-scope .pt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--pt-brand);
  border-radius: var(--pt-radius-md);
  background: var(--pt-brand);
  font-family: var(--pt-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.2px;
  text-transform: none;
  text-decoration: none;
  white-space: nowrap;
  color: #ffffff;
  box-shadow: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.pt-scope .pt-btn:hover,
.pt-scope .pt-btn:focus {
  background: var(--pt-brand-hover);
  border-color: var(--pt-brand-hover);
  color: #ffffff;
  text-decoration: none;
}

.pt-scope .pt-btn i { font-size: 13px; }

.pt-scope .pt-btn.is-ghost {
  background: var(--pt-surface);
  border-color: var(--pt-border-strong);
  color: var(--pt-text-soft);
}

.pt-scope .pt-btn.is-ghost:hover,
.pt-scope .pt-btn.is-ghost:focus {
  background: var(--pt-surface-sunken);
  border-color: var(--pt-text-muted);
  color: var(--pt-text);
}

.pt-scope .pt-btn.is-danger {
  background: var(--pt-surface);
  border-color: var(--pt-danger-border);
  color: var(--pt-danger);
}
.pt-scope .pt-btn.is-danger:hover,
.pt-scope .pt-btn.is-danger:focus {
  background: var(--pt-danger-bg);
  border-color: var(--pt-danger);
  color: var(--pt-danger);
}

/* The one solid destructive button, inside the confirm dialog only. Outside a
   dialog nothing on these screens deletes without being asked twice. */
.pt-scope .pt-btn.is-solid-danger {
  background: var(--pt-danger);
  border-color: var(--pt-danger);
  color: #ffffff;
}

.pt-scope .pt-btn.is-solid-danger:hover,
.pt-scope .pt-btn.is-solid-danger:focus {
  background: var(--pt-brand-hover);
  border-color: var(--pt-brand-hover);
  color: #ffffff;
}

/* Copy reports itself by going green for two seconds. The old page reported
   "Copied!" in a fixed toast, and reported it even when the clipboard write had
   thrown — which it does on http, where these vaults are most likely opened. */
.pt-scope .pt-btn.is-done {
  background: var(--pt-good-bg);
  border-color: var(--pt-good-border);
  color: var(--pt-good);
}

.pt-scope .pt-btn[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

/* A 28px button for the tools inside the reveal panel's header, where a 34px
   one crowds the snippet's name. */
.pt-scope .pt-btn.is-sm {
  height: 28px;
  padding: 0 10px;
  font-size: 11px;
  gap: 5px;
}

.pt-scope .pt-btn.is-sm i { font-size: 12px; }

/* Pushes everything after it to the right of a row. */
.pt-scope .pt-spacer { margin-left: auto; }
/* ==========================================================================
   THE RAIL — the list of snippet names, and the only way to reorder them.

   Everything in this block that moves is hidden until the script announces
   itself by putting `.is-enhanced` on the scope root. That is deliberate and it
   is the whole no-JavaScript story for this screen: without a script every
   snippet panel is simply open, stacked down the page, and each chip is an
   anchor to one of them. Nothing inert is ever shown — no drag handle that
   cannot drag, no Copy button that cannot copy.

   What this replaces: a CDN copy of Sortable 1.14.0 (an off-site request on a
   page whose entire purpose is holding secrets), driving a grid of `col-lg-4`
   cards whose titles linked to the *edit form* — there was no way to read a
   snippet at all, `{{ entry.content }}` was rendered nowhere, and reordering was
   possible only with a mouse.
   ========================================================================== */
.pt-scope .pt-rail {
  display: grid;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pt-scope .pt-chip {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px;
  padding: 0 6px 0 0;
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.pt-scope .pt-chip:hover { border-color: var(--pt-border-strong); }

.pt-scope .pt-chip.is-active {
  border-color: var(--pt-brand);
  background: var(--pt-brand-bg);
}

/* The chip being carried. Opacity rather than a transform: a transform would
   make it the containing block for its own children and the browser's drag
   image is already the visual feedback. */
.pt-scope .pt-chip.is-dragging { opacity: 0.45; }
/* Where the carried chip would land. An inset shadow rather than a border, so
   the row does not change height as the pointer crosses it — a 1px border
   appearing on hover moves every chip below it by a pixel, which reads as the
   list flickering. */
.pt-scope .pt-chip.is-over {
  box-shadow: inset 0 2px 0 0 var(--pt-brand);
}

.pt-scope .pt-chip.is-over-below {
  box-shadow: inset 0 -2px 0 0 var(--pt-brand);
}

/* The grip. Cursor `grab` is the only affordance a native HTML5 drag has —
   there is no library drawing a ghost — so it matters that it is on the handle
   and not on the whole chip, whose middle third is a link.

   The three children are placed by column rather than by source order, because
   two of them are conditional: a read-only visitor gets no grip and no chevrons
   at all, and without an explicit `grid-column` the name would slide into the
   `auto` track and size itself to its longest word instead of being clipped. */
.pt-scope .pt-chip__grip {
  display: none;
  grid-column: 1;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 34px;
  font-size: 13px;
  color: var(--pt-text-muted);
  cursor: grab;
}

.pt-scope .pt-chip.is-dragging .pt-chip__grip { cursor: grabbing; }
.pt-scope.is-enhanced .pt-chip__grip { display: flex; }

/* The name. An <a> to the panel below with no script, intercepted by the script
   when there is one — the same href-first idiom the short-link dialogs use. */
.pt-scope .pt-chip__name {
  display: block;
  grid-column: 2;
  min-width: 0;
  padding: 8px 6px;
  border: 0;
  background: transparent;
  font-family: var(--pt-font-display);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.2px;
  text-align: left;
  text-decoration: none;
  color: var(--pt-text);
  cursor: pointer;
}

.pt-scope .pt-chip__name:hover,
.pt-scope .pt-chip__name:focus { color: var(--pt-brand); text-decoration: none; }
.pt-scope .pt-chip.is-active .pt-chip__name { color: var(--pt-brand); }

/* One line, clipped. A 50-character snippet name in a 300px rail wraps to three
   lines and every chip in the list becomes a different height. */
.pt-scope .pt-chip__label {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.pt-scope .pt-chip__meta {
  display: block;
  font-family: var(--pt-font-body);
  font-size: 11px;
  font-weight: 400;
  line-height: 16px;
  color: var(--pt-text-muted);
}

/* Move up / move down. The keyboard route to the same thing the grip does, and
   the only route on a touch screen — HTML5 drag events do not fire for touch.
   Hidden until the script is running, like the grip. */
.pt-scope .pt-chip__moves { display: none; grid-column: 3; gap: 1px; }
.pt-scope.is-enhanced .pt-chip__moves { display: flex; }

.pt-scope .pt-move {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--pt-radius-sm);
  background: transparent;
  font-size: 11px;
  line-height: 1;
  color: var(--pt-text-muted);
  cursor: pointer;
}

.pt-scope .pt-move:hover,
.pt-scope .pt-move:focus {
  outline: none;
  border-color: var(--pt-border-strong);
  background: var(--pt-surface);
  color: var(--pt-brand);
}

.pt-scope .pt-move[disabled] { opacity: 0.35; pointer-events: none; }
/* How to reorder, said once under the rail, and only when reordering is
   possible at all — the template omits it for a read-only visitor. */
.pt-scope .pt-rail__hint {
  display: none;
  margin: 10px 0 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--pt-text-muted) !important;
}

.pt-scope.is-enhanced .pt-rail__hint { display: block; }

/* Whether the new order reached the server. The old page posted the order and
   ignored the answer entirely: a 400 from the endpoint left the chips sitting in
   their new places, and the next reload put them back. */
.pt-scope .pt-rail__state {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 8px 0 0;
  font-size: 11px;
  line-height: 17px;
  color: var(--pt-text-muted) !important;
}

.pt-scope .pt-rail__state[hidden] { display: none; }
.pt-scope .pt-rail__state i { font-size: 12px; }
.pt-scope .pt-rail__state.is-good { color: var(--pt-good) !important; }
.pt-scope .pt-rail__state.is-bad { color: var(--pt-danger) !important; }
/* ==========================================================================
   THE REVEAL — one snippet, opened in place.

   Approved shape: click a name and its contents open beside the rail with Copy,
   Print and Edit, no page load, one open at a time. Every panel is in the
   document already (the vault is unlocked; the content is authorised), so
   opening one is a class change, not a request — and with no script they are all
   simply open, which is why `display: none` here is keyed off `.is-enhanced`.
   ========================================================================== */
.pt-scope .pt-reveal {
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface);
  overflow: hidden;
}

.pt-scope .pt-reveal + .pt-reveal { margin-top: 16px; }
.pt-scope.is-enhanced .pt-reveal { display: none; margin-top: 0; }
.pt-scope.is-enhanced .pt-reveal.is-open { display: block; }
.pt-scope .pt-reveal__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--pt-border);
  background: var(--pt-surface-muted);
}

/* An <h2>, so !important — see the top of the file. */
.pt-scope .pt-reveal__title {
  margin: 0;
  font-family: var(--pt-font-display) !important;
  font-size: 14px !important;
  font-weight: 700 !important;
  line-height: 20px !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
  word-break: break-word;
}

.pt-scope .pt-reveal__meta {
  margin: 2px 0 0;
  font-size: 11px;
  line-height: 16px;
  color: var(--pt-text-muted) !important;
}

/* Copy and Print: script-only, so hidden until the script is running. */
.pt-scope .pt-reveal__tools { display: none; gap: 6px; margin-left: auto; }
.pt-scope.is-enhanced .pt-reveal__tools { display: flex; flex-wrap: wrap; }

/* Edit and Delete: a link and a form, so they work either way. */
.pt-scope .pt-reveal__links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pt-scope .pt-reveal__links form { margin: 0; }
.pt-scope.is-enhanced .pt-reveal__links { margin-left: 0; }

.pt-scope .pt-reveal__body {
  padding: 16px;
  font-size: 13px;
  line-height: 22px;
  color: var(--pt-text);
  overflow-wrap: anywhere;
}
/* --------------------------------------------------------------------------
   Inside the panel: whatever CKEditor saved. Arbitrary rich text, so the rules
   below are element rules rather than classes, and the headings among them get
   the same !important treatment as the page's own — a visitor's `<h2>` in a
   snippet would otherwise print at 24px capitalised black inside a 13px panel.

   `word-break` and `overflow-wrap` matter more here than anywhere else on the
   dashboard: the thing people keep in these vaults is a 64-character token with
   no spaces in it, and without a break rule it widens the grid column until the
   rail is pushed off the screen.
   -------------------------------------------------------------------------- */
.pt-scope .pt-reveal__body p {
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 22px;
  color: var(--pt-text) !important;
}

.pt-scope .pt-reveal__body > :last-child { margin-bottom: 0; }

.pt-scope .pt-reveal__body h1,
.pt-scope .pt-reveal__body h2,
.pt-scope .pt-reveal__body h3,
.pt-scope .pt-reveal__body h4,
.pt-scope .pt-reveal__body h5,
.pt-scope .pt-reveal__body h6 {
  margin: 16px 0 7px;
  font-family: var(--pt-font-display) !important;
  font-weight: 700 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
}

.pt-scope .pt-reveal__body h1 { font-size: 17px !important; line-height: 24px !important; }
.pt-scope .pt-reveal__body h2 { font-size: 15px !important; line-height: 22px !important; }
.pt-scope .pt-reveal__body h3,
.pt-scope .pt-reveal__body h4,
.pt-scope .pt-reveal__body h5,
.pt-scope .pt-reveal__body h6 { font-size: 14px !important; line-height: 21px !important; }

.pt-scope .pt-reveal__body ul,
.pt-scope .pt-reveal__body ol { margin: 0 0 10px; padding-left: 20px; }
.pt-scope .pt-reveal__body li { margin: 0 0 4px; }

.pt-scope .pt-reveal__body a {
  color: var(--pt-brand);
  text-decoration: underline;
  word-break: break-all;
}
.pt-scope .pt-reveal__body code,
.pt-scope .pt-reveal__body kbd,
.pt-scope .pt-reveal__body samp {
  padding: 1px 5px;
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-sm);
  background: var(--pt-surface-sunken);
  font-family: var(--pt-font-mono);
  font-size: 12px;
  color: var(--pt-text);
}

.pt-scope .pt-reveal__body pre {
  margin: 0 0 10px;
  padding: 11px 13px;
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-sm);
  background: var(--pt-surface-sunken);
  font-family: var(--pt-font-mono);
  font-size: 12px;
  line-height: 20px;
  color: var(--pt-text);
  white-space: pre-wrap;
  word-break: break-word;
}

.pt-scope .pt-reveal__body pre code {
  padding: 0;
  border: 0;
  background: transparent;
}

.pt-scope .pt-reveal__body blockquote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--pt-border-strong);
  color: var(--pt-text-soft);
}

.pt-scope .pt-reveal__body img {
  max-width: 100%;
  height: auto;
  border-radius: var(--pt-radius-sm);
}

.pt-scope .pt-reveal__body table {
  width: 100%;
  margin: 0 0 10px;
  border-collapse: collapse;
  font-size: 12px;
}
.pt-scope .pt-reveal__body th,
.pt-scope .pt-reveal__body td {
  padding: 6px 9px;
  border: 1px solid var(--pt-border);
  text-align: left;
  vertical-align: top;
}

.pt-scope .pt-reveal__body th {
  background: var(--pt-surface-sunken);
  font-weight: 700;
}

.pt-scope .pt-reveal__body hr {
  margin: 12px 0;
  border: 0;
  border-top: 1px solid var(--pt-border);
}


/* ==========================================================================
   FACTS — the expiry / visit-count read-out
   --------------------------------------------------------------------------
   view_text.html printed neither. `max_access` silently retires a vault the
   moment the count reaches it, and `expires_at` does the same at its date, so
   a vault could stop answering with nothing on the page having ever hinted
   that it was on a clock. The <meter> is the browser's own bar: no chart
   library, and it reads out to assistive tech as a value in a range.
   ========================================================================== */

.pt-scope .pt-facts {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
  margin: 0;
}

/* The vault card carries the address first and the two facts under it, and this
   is the only place the two blocks ever meet, so the gap lives on the pair
   rather than becoming a margin every `.pt-facts` has to carry. */
.pt-scope .pt-share + .pt-facts { margin-top: 14px; }

.pt-scope .pt-fact {
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface-muted);
}
.pt-scope .pt-fact__label {
  display: block;
  margin: 0 0 4px;
  font-family: var(--pt-font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--pt-text-muted);
}

.pt-scope .pt-fact__value {
  display: block;
  font-family: var(--pt-font-display);
  font-size: 15px;
  font-weight: 700;
  line-height: 21px;
  color: var(--pt-text);
  overflow-wrap: anywhere;
}

.pt-scope .pt-fact__value.is-warn {
  color: var(--pt-warn);
}

.pt-scope .pt-fact__note {
  display: block;
  margin: 3px 0 0;
  font-size: 12px;
  line-height: 18px;
  color: var(--pt-text-soft) !important;
}

.pt-scope .pt-fact meter {
  display: block;
  width: 100%;
  height: 6px;
  margin: 7px 0 0;
}
/* --------------------------------------------------------------------------
   SHARE — the vault's own address, and a button that copies it.

   The address is the whole distribution mechanism for a vault: there is no
   invite, no email on the model, nothing to send but the URL and the password.
   view_text.html printed the identifier as plain text in a heading and left the
   visitor to reconstruct the link from the address bar.
   -------------------------------------------------------------------------- */

.pt-scope .pt-share {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}

.pt-scope .pt-share__url {
  min-width: 0;
  padding: 9px 11px;
  border: 1px solid var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface-sunken);
  font-family: var(--pt-font-mono);
  font-size: 12px;
  line-height: 18px;
  color: var(--pt-text) !important;
  overflow-wrap: anywhere;
}


/* --------------------------------------------------------------------------
   EMPTY — a vault with no snippets yet, and a rail with nothing in it.
   -------------------------------------------------------------------------- */

.pt-scope .pt-empty {
  padding: 26px 18px;
  border: 1px dashed var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  background: var(--pt-surface-muted);
  text-align: center;
}
.pt-scope .pt-empty i {
  display: block;
  margin: 0 0 8px;
  font-size: 26px;
  color: var(--pt-text-muted);
}

.pt-scope .pt-empty__title {
  margin: 0 0 4px;
  font-family: var(--pt-font-display) !important;
  font-size: 14px !important;
  font-weight: 700 !important;
  line-height: 20px !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
}

.pt-scope .pt-empty p {
  margin: 0;
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-text-soft) !important;
}

.pt-scope .pt-empty .pt-btn {
  margin-top: 12px;
}

.pt-scope .pt-rail__empty {
  padding: 16px 12px;
  border: 1px dashed var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  font-size: 12px;
  line-height: 18px;
  text-align: center;
  color: var(--pt-text-muted) !important;
}
/* ==========================================================================
   DIALOGS — Bootstrap's .modal, styled from the inside only
   --------------------------------------------------------------------------
   view_text.html shipped its own overlay: `.cs-modal-backdrop` at
   `z-index: 9999999 !important`, a `.cs-modal` at 10000000, and two blocks of
   JavaScript to open and close them. That existed because `.main-section`
   carried its own stacking context, so a real Bootstrap dialog opened BEHIND
   the page. The shell fixed that on 2026-09-01 — `.main-section:has(
   .user-dashboard) { z-index: auto !important }` with the backdrop at 1000000
   and `.modal` at 1000001, pinned by accounts.tests.DashboardShellStackingTests
   — so the hand-rolled pair is gone and nothing below sets a z-index.

   The vendored bundle (assets/bootstrap/js/bootstrap.bundle.min.js) is 5.1.3,
   so the markup uses data-bs-toggle / data-bs-target / data-bs-dismiss. The
   4.3.1 CDN reference in the shell is a stylesheet only and cannot open a
   dialog; anything written to `data-toggle` would sit there inert.
   ========================================================================== */

.pt-scope .pt-modal {
  border: 1px solid var(--pt-border);
  border-radius: var(--pt-radius-md);
  box-shadow: 0 18px 44px rgba(47, 49, 58, 0.18);
}

.pt-scope .pt-modal .modal-body {
  padding: 24px;
  text-align: center;
}

.pt-scope .pt-modal__chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 13px;
  border: 1px solid var(--pt-danger-border);
  border-radius: 50%;
  background: var(--pt-danger-bg);
  font-size: 19px;
  color: var(--pt-danger);
}
.pt-scope .pt-modal__title {
  margin: 0 0 6px;
  font-family: var(--pt-font-display) !important;
  font-size: 16px !important;
  font-weight: 700 !important;
  line-height: 23px !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  color: var(--pt-text) !important;
}

.pt-scope .pt-modal__subject {
  margin: 0 0 14px;
  font-family: var(--pt-font-mono);
  font-size: 13px;
  line-height: 19px;
  color: var(--pt-brand) !important;
  overflow-wrap: anywhere;
}

.pt-scope .pt-modal__warn {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 18px;
  padding: 11px 13px;
  border: 1px solid var(--pt-danger-border);
  border-radius: var(--pt-radius-md);
  background: var(--pt-danger-bg);
  font-size: 12px;
  line-height: 18px;
  text-align: left;
  color: var(--pt-danger) !important;
}

.pt-scope .pt-modal__warn i {
  margin-top: 1px;
  flex: none;
  font-size: 14px;
}

.pt-scope .pt-modal__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  flex-wrap: wrap;
}

.pt-scope .pt-modal__actions form {
  margin: 0;
}
/* --------------------------------------------------------------------------
   NO-SCRIPT, NOTES, and the CKEditor container.

   The vault degrades rather than breaks: with no script every reveal panel is
   open, the chips are plain in-page anchors, and Edit and Delete are a link and
   a form. So this notice explains what is *missing* (copy, print, drag) rather
   than announcing that the page does not work.
   -------------------------------------------------------------------------- */

.pt-scope .pt-noscript {
  margin: 0 0 16px;
  padding: 11px 14px;
  border: 1px solid var(--pt-warn-border);
  border-radius: var(--pt-radius-sm);
  background: var(--pt-warn-bg);
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-warn) !important;
}

.pt-scope .pt-notes {
  margin: 0;
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-text-soft) !important;
}

/* `margin: 0` above is right for the usual case — notes sitting inside a
   `.pt-card__body`, which supplies its own padding. It was wrong for the two
   places a notes block follows a sibling: the vault screen closes with notes
   directly after `.pt-columns`, and `.pt-facts` is followed by notes inside a
   card body. Neither adjacency had a rule, so both pairs sat flush. 14px inside
   a body matches `.pt-share + .pt-facts`; 16px between top-level blocks matches
   `.pt-card + .pt-card`. */
.pt-scope .pt-facts + .pt-notes { margin-top: 14px; }
.pt-scope .pt-columns + .pt-notes { margin-top: 16px; }

.pt-scope .pt-notes__list {
  margin: 0;
  padding: 0 0 0 17px;
  list-style: disc;
}

.pt-scope .pt-notes__list li {
  margin: 0 0 4px;
  font-size: 12px;
  line-height: 19px;
  color: var(--pt-text-soft);
}

.pt-scope .pt-notes__list li:last-child {
  margin-bottom: 0;
}

.pt-scope .pt-notes__list strong {
  font-weight: 700;
  color: var(--pt-text);
}
/* CKEditor draws its own chrome into an iframe, so all this scope can do is give
   the replaced <textarea> a sensible box. The editor is loaded by ckeditor's own
   `{{ form.media }}` from this server's static files — the widget is
   RichTextUploadingField, not a CDN build. */
.pt-scope .pt-editor {
  max-width: 100%;
}

.pt-scope .pt-editor textarea {
  width: 100%;
  min-height: 220px;
  padding: 11px;
  border: 1px solid var(--pt-border-strong);
  border-radius: var(--pt-radius-md);
  font-family: var(--pt-font-body);
  font-size: 13px;
  line-height: 21px;
  color: var(--pt-text);
}

.pt-scope .pt-editor .cke_chrome {
  border-color: var(--pt-border-strong);
}


/* --------------------------------------------------------------------------
   The print frame. Print sends the open snippet alone to the printer rather
   than the whole dashboard — sidebar, header and all — by writing it into an
   iframe in this same document and calling print() on that. Off-screen and
   hidden, never `display: none`: a frame that is not rendered has no window to
   print from.
   -------------------------------------------------------------------------- */
.pt-scope .pt-frame {
  position: absolute;
  width: 0;
  height: 0;
  border: 0;
  visibility: hidden;
}
/* --------------------------------------------------------------------------
   Reduced motion. Nothing here animates except hover transitions and the chip
   that lifts while dragged, all of them decorations.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .pt-scope .pt-btn,
  .pt-scope .pt-chip,
  .pt-scope .pt-move,
  .pt-scope .pt-field input,
  .pt-scope .pt-input {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   RESPONSIVE. The dashboard column is ~1000px at lg and ~730px between the
   sidebar breakpoints, so the rail and the reveal stop being side-by-side well
   before a phone: 300px of chips beside a snippet is a squeeze at 730px.
   -------------------------------------------------------------------------- */
@media (max-width: 991px) {
  .pt-scope .pt-columns {
    grid-template-columns: minmax(0, 1fr);
  }

  .pt-scope .pt-rail {
    max-height: 260px;
    overflow-y: auto;
  }
}

@media (max-width: 640px) {
  .pt-scope {
    padding-bottom: 24px;
  }

  .pt-scope .pt-title {
    font-size: 18px !important;
    line-height: 25px !important;
  }

  .pt-scope .pt-card__body,
  .pt-scope .pt-modal .modal-body {
    padding: 14px;
  }

  .pt-scope .pt-form,
  .pt-scope .pt-facts {
    grid-template-columns: minmax(0, 1fr);
  }

  .pt-scope .pt-share {
    grid-template-columns: minmax(0, 1fr);
  }

  .pt-scope .pt-btn {
    flex: 1 1 132px;
  }

  .pt-scope .pt-spacer {
    margin-left: 0;
  }
}
/* --------------------------------------------------------------------------
   Ctrl+P on the vault itself. The Print button prints the open snippet alone
   through the frame above, which is the better route; this is what happens when
   somebody prints the page instead. Everything that is not the snippet goes.
   -------------------------------------------------------------------------- */
@media print {
  .pt-scope {
    padding: 0;
  }

  .pt-scope .pt-noprint,
  .pt-scope .pt-rail,
  .pt-scope .pt-rail__hint,
  .pt-scope .pt-rail__state,
  .pt-scope .pt-actions,
  .pt-scope .pt-reveal__tools,
  .pt-scope .pt-reveal__links,
  .pt-scope .pt-share,
  .pt-scope .pt-noscript {
    display: none !important;
  }

  .pt-scope .pt-columns {
    display: block;
  }

  .pt-scope .pt-card,
  .pt-scope .pt-reveal {
    border: 0;
    box-shadow: none;
  }

  .pt-scope .pt-card__body {
    padding: 0;
  }

  .pt-scope .pt-reveal__body {
    color: #000000 !important;
  }

  .pt-scope .pt-reveal__body p,
  .pt-scope .pt-reveal__body li {
    color: #000000 !important;
  }
}
