/*
 * com_comicstrip frontend styles.
 * Everything is scoped under .com-comicstrip so nothing here leaks onto or
 * clashes with the Gantry 5 template's global styles elsewhere on the site.
 */

.com-comicstrip {
    --cx-border: #e2e5eb;
    --cx-border-strong: #cbd1db;
    --cx-bg: #f4f6fb;
    --cx-surface: #ffffff;
    --cx-accent: #4f46e5;
    --cx-accent-dark: #4338ca;
    --cx-accent-soft: #eef0fe;
    --cx-danger: #dc2626;
    --cx-danger-soft: #fdecec;
    --cx-success: #16a34a;
    --cx-warning: #b45309;
    --cx-text: #1c2130;
    --cx-text-muted: #6b7280;
    --cx-radius: 10px;
    --cx-radius-sm: 6px;
    --cx-shadow-sm: 0 1px 2px rgba(17, 24, 39, 0.05);
    --cx-shadow-md: 0 6px 20px rgba(17, 24, 39, 0.08);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    box-sizing: border-box;
    color: var(--cx-text);
    line-height: 1.5;
}

.com-comicstrip *,
.com-comicstrip *::before,
.com-comicstrip *::after {
    box-sizing: inherit;
}

.com-comicstrip h1 {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0 0 0.25rem;
}

.com-comicstrip a {
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.com-comicstrip :focus-visible {
    outline: 2px solid var(--cx-accent);
    outline-offset: 2px;
}

/* ---------- Page-transition loader ---------- */
/* Not scoped under .com-comicstrip — page-loader.js appends this directly
   to <body>, outside the component's own markup, so it still shows during
   the moment the NEXT page (which hasn't rendered .com-comicstrip yet) is
   loading. */

.cx-page-loader {
    position: fixed;
    inset: 0;
    z-index: 100000;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(244, 246, 251, 0.75);
    backdrop-filter: blur(1px);
}

.cx-page-loader.is-visible {
    display: flex;
}

.cx-page-loader-spinner {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    border: 4px solid #d7dbe8;
    border-top-color: #4f46e5;
    animation: cx-spin 0.7s linear infinite;
}

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

/* ---------- Guest CTA / sandbox banner ---------- */

.cx-guest-cta {
    max-width: 640px;
    margin: 3rem auto;
    text-align: center;
    padding: 2.5rem 2rem;
    background: linear-gradient(180deg, var(--cx-accent-soft) 0%, var(--cx-surface) 65%);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-sm);
}

.cx-guest-cta h1 {
    font-size: 1.75rem;
}

.cx-guest-cta p {
    color: var(--cx-text-muted);
    margin: 0.5rem 0;
}

.cx-guest-cta-actions {
    display: flex;
    justify-content: center;
    gap: 0.75rem;
    margin: 1.5rem 0 1rem;
}

.cx-guest-cta-note {
    font-size: 0.85rem;
    color: var(--cx-text-muted);
}

.cx-sandbox-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    background: #fff7e6;
    border: 1px solid #f3c969;
    border-radius: var(--cx-radius-sm);
    padding: 0.65rem 1rem;
    margin-bottom: 0.75rem;
    font-size: 0.9rem;
    color: #7c4a03;
}

/* ---------- Guest sandbox: read-only preview grid (?preview=1) ---------- */
/* Panels vary in shape (see PanelSizeHelper::PRESETS), so unlike a fixed
   16:9 frame grid, cards size to their own thumbnail's natural aspect
   ratio rather than a forced one. */

.cx-sandbox-preview {
    padding: 1rem 0;
}

.cx-sandbox-preview-description {
    margin: 0 0 0.85rem;
    font-size: 0.9rem;
    color: var(--cx-text-muted);
}

.cx-sandbox-preview-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1.25rem;
}

.cx-sandbox-preview-card {
    position: relative;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    overflow: hidden;
    background: var(--cx-surface);
}

.cx-sandbox-preview-card img {
    display: block;
    width: 100%;
    height: auto;
}

.cx-sandbox-preview-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    background: var(--cx-bg);
    color: var(--cx-text-muted);
    font-size: 2rem;
}

.cx-sandbox-preview-number {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 4px;
    padding: 0.1rem 0.5rem;
    font-weight: 600;
    font-size: 0.8rem;
    color: var(--cx-text);
}

/* ---------- Buttons ---------- */

.com-comicstrip .cx-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-text);
    text-decoration: none;
    cursor: pointer;
    font-size: 0.9rem;
    font-weight: 500;
    line-height: 1.2;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.com-comicstrip .cx-btn:hover {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
    background: var(--cx-accent-soft);
}

.com-comicstrip .cx-btn:active {
    transform: translateY(1px);
}

.com-comicstrip .cx-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

.com-comicstrip .cx-btn:disabled:hover {
    border-color: var(--cx-border-strong);
    background: var(--cx-surface);
    color: var(--cx-text);
}

.com-comicstrip .cx-btn-block {
    width: 100%;
    justify-content: center;
}

/* .cx-btn's own display:inline-flex has the same specificity as the
   browser's default [hidden]{display:none} UA rule, and an author
   stylesheet rule always wins that tie — without this, a hidden .cx-btn
   (e.g. #cx-btn-resubmit) renders anyway. */
.com-comicstrip .cx-btn[hidden] {
    display: none;
}

/* Primary call-to-action buttons — New Comic Strip, Add Panel, Try It Now —
   deliberately stand out as solid/filled so the main action on any screen
   is unambiguous at a glance. Explicit class rather than a structural
   selector, so secondary actions sharing the same <form><button> shape
   (e.g. Duplicate) don't get pulled in by accident. */
.com-comicstrip .cx-btn-primary {
    background: var(--cx-accent);
    border-color: var(--cx-accent);
    color: #fff;
    box-shadow: var(--cx-shadow-sm);
}

.com-comicstrip .cx-btn-primary:hover {
    background: var(--cx-accent-dark);
    border-color: var(--cx-accent-dark);
    color: #fff;
}

.com-comicstrip .cx-btn-danger {
    color: var(--cx-danger);
    border-color: var(--cx-danger);
}

.com-comicstrip .cx-btn-danger:hover {
    background: var(--cx-danger-soft);
    color: var(--cx-danger);
    border-color: var(--cx-danger);
}

/* ---------- Editor ---------- */

.com-comicstrip-editor {
    display: flex;
    flex-direction: column;
    height: calc(100vh - 120px);
    min-height: 560px;
}

.cx-editor-header {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.3rem 0;
    border-bottom: 1px solid var(--cx-border);
}

.cx-editor-title-group {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* Review-status badge stacked above the autosave text instead of sitting
   inline next to the title — keeps the title row from having to make room
   for it horizontally. */
.cx-editor-status-group {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.2rem;
    flex-shrink: 0;
}

.cx-editor-title-input,
.cx-editor-desc-input,
.cx-editor-title-display,
.cx-editor-desc-display {
    border: 1px solid transparent;
    border-radius: var(--cx-radius-sm);
    background: transparent;
    padding: 0.1rem 0.4rem;
    margin: 0 0 0 -0.4rem;
    width: 100%;
    font-family: inherit;
    color: var(--cx-text);
    line-height: 1.25;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.cx-editor-title-input,
.cx-editor-title-display {
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.cx-editor-desc-input,
.cx-editor-desc-display {
    font-size: 0.72rem;
    color: var(--cx-text-muted);
}

.cx-editor-title-input:hover,
.cx-editor-desc-input:hover {
    border-color: var(--cx-border);
}

.cx-editor-title-input:focus,
.cx-editor-desc-input:focus {
    outline: none;
    border-color: var(--cx-accent);
    background: var(--cx-surface);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}

/* Click-to-edit display text — a clickable placeholder for the real
   <input>, which stays hidden (see the `hidden` attribute in the
   template) until setupInlineEdit() in editor.js swaps them. */
.cx-editor-title-display,
.cx-editor-desc-display {
    display: block;
    margin-top: 0;
    margin-bottom: 0;
    cursor: pointer;
    border: 1px dashed transparent;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cx-editor-title-display:hover,
.cx-editor-desc-display:hover {
    border-color: var(--cx-border-strong);
    background: var(--cx-bg);
}

.cx-editor-desc-display.is-placeholder {
    font-style: italic;
    opacity: 0.8;
}

.cx-editor-title-static,
.cx-editor-desc-static {
    cursor: default;
    margin: 0 0 0 -0.4rem;
    padding: 0.1rem 0.4rem;
}

.cx-editor-title-static:hover,
.cx-editor-desc-static:hover {
    background: none;
    border-color: transparent;
}

.cx-review-badge {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    background: var(--cx-bg);
    color: var(--cx-text-muted);
}

.cx-review-badge[data-status="submitted"] {
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-review-badge[data-status="reviewed"] {
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-review-badge[data-status="changes_requested"] {
    background: var(--cx-danger-soft);
    color: var(--cx-danger);
}

.cx-review-badge[data-status="approved"] {
    background: #e9f7ef;
    color: var(--cx-success);
}

.cx-review-badge[data-status="resubmitted"] {
    background: #fff3da;
    color: var(--cx-warning);
}

/* A small "there's feedback on this" marker — next to a comic strip's title
   (dashboard card, teacher list, editor header) when it has any teacher
   comment at all, independent of the review-status badge above (which
   reflects a teacher's explicit reviewed/changes/approved verdict, not
   whether feedback exists) and independent of read/unread (see
   .cx-feedback-badge below for the unread count on the bell icon). */
.cx-feedback-indicator {
    color: var(--cx-accent);
    font-size: 0.85em;
    margin-left: 0.3rem;
    vertical-align: middle;
}

.cx-feedback-indicator[hidden] {
    display: none;
}

.cx-feedback-bell {
    position: relative;
    /* Compact icon-only button — sits inline next to the review-status
       badge under the title now, not in the toolbar-style actions row it
       used to live in, so the default .cx-btn padding is too roomy. */
    width: 30px;
    height: 30px;
    padding: 0;
    justify-content: center;
    flex-shrink: 0;
}

/* Blue for "there's feedback, already read"; green for "something new" —
   see updateFeedbackBadge() in editor.js/comicpage.js. The two are mutually
   exclusive so the colour always means one specific thing, not just
   "a comment exists somewhere". */
.cx-feedback-bell.has-feedback {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
    background: var(--cx-accent-soft);
}

.cx-feedback-bell.has-unread {
    border-color: var(--cx-success);
    color: var(--cx-success);
    background: #e9f7ef;
}

.cx-feedback-badge {
    position: absolute;
    top: -0.4rem;
    right: -0.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--cx-danger);
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    line-height: 1;
}

.cx-review-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--cx-border);
}

.cx-review-actions .cx-btn {
    flex: 1;
    justify-content: center;
    font-size: 0.78rem;
    padding: 0.4rem 0.5rem;
}

.cx-editor-status {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cx-text-muted);
    min-width: 8rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.cx-editor-status::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.cx-editor-status[data-state="saving"] { color: var(--cx-accent); }
.cx-editor-status[data-state="saved"] { color: var(--cx-success); }
.cx-editor-status[data-state="unsaved"] { color: var(--cx-warning); }
.cx-editor-status[data-state="save-failed"],
.cx-editor-status[data-state="conflict"] { color: var(--cx-danger); }

.cx-editor-actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.cx-btn-save {
    color: var(--cx-success);
    border-color: var(--cx-success);
}

.cx-btn-save:hover {
    background: #e9f7ef;
    color: var(--cx-success);
    border-color: var(--cx-success);
}

/* "Back to Dashboard" leaves the editor entirely, unlike everything else
   in this bar — set apart with a divider and pushed to the far edge so a
   stray click among the frequently-used buttons can't land on it. */
.cx-btn-back {
    margin-left: 0.75rem;
    padding-left: 0.9rem;
    border-left: 1px solid var(--cx-border);
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    color: var(--cx-text-muted);
}

.cx-btn-back:hover {
    color: var(--cx-danger);
    border-color: var(--cx-border);
    border-left-color: var(--cx-border);
    background: var(--cx-danger-soft);
}

.cx-editor-body {
    flex: 1;
    display: grid;
    /* filmstrip | canvas — the properties panel (.cx-properties-panel)
       isn't a column of its own; it floats over the canvas (see
       .cx-canvas-stage) so it costs no permanent width. */
    grid-template-columns: 140px 1fr;
    gap: 0.6rem;
    min-height: 0;
    margin-top: 0.75rem;
}

.cx-filmstrip {
    overflow-y: auto;
    border-right: 1px solid var(--cx-border);
    padding-right: 0.4rem;
}

.cx-filmstrip-list {
    list-style: none;
    margin: 0 0 0.6rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.cx-filmstrip-item {
    border: 2px solid transparent;
    border-radius: var(--cx-radius-sm);
    padding: 0.3rem;
    cursor: pointer;
    position: relative;
    background: var(--cx-surface);
    box-shadow: var(--cx-shadow-sm);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.cx-filmstrip-item:hover {
    box-shadow: var(--cx-shadow-md);
}

.cx-filmstrip-item.is-active {
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}

/* "This panel has teacher feedback" — the icon itself is always in the
   DOM (see renderFilmstrip() in editor.js) and only made visible when its
   parent .cx-filmstrip-item gets the has-feedback class, toggled by
   updateFeedbackIndicators() independently of when the comment list loads. */
.cx-filmstrip-feedback-icon {
    display: none;
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    width: 0.9rem;
    height: 0.9rem;
    line-height: 0.9rem;
    border-radius: 50%;
    background: var(--cx-accent);
    color: #fff;
    font-size: 0.55rem;
    text-align: center;
    box-shadow: 0 0 0 1.5px var(--cx-surface);
}

.cx-filmstrip-item.has-feedback .cx-filmstrip-feedback-icon {
    display: block;
}

.cx-filmstrip-thumb {
    aspect-ratio: 16 / 9;
    background: var(--cx-bg);
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    color: var(--cx-text-muted);
    font-weight: 600;
}

.cx-filmstrip-thumb img { width: 100%; height: 100%; object-fit: cover; }

.cx-filmstrip-number {
    display: block;
    text-align: center;
    font-size: 0.78rem;
    color: var(--cx-text-muted);
    padding-top: 0.15rem;
}

.cx-filmstrip-item.is-active .cx-filmstrip-number {
    color: var(--cx-accent-dark);
    font-weight: 600;
}

.cx-filmstrip-actions {
    position: absolute;
    top: 4px;
    right: 4px;
    display: none;
    gap: 3px;
}

.cx-filmstrip-item:hover .cx-filmstrip-actions { display: flex; }

.cx-filmstrip-actions button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    line-height: 1;
    font-size: 0.75rem;
    background: rgba(255, 255, 255, 0.95);
    border: 1px solid var(--cx-border);
    border-radius: 4px;
    cursor: pointer;
    width: 24px;
    height: 24px;
    box-shadow: var(--cx-shadow-sm);
}

.cx-filmstrip-actions button:hover {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
}

.cx-canvas-area {
    display: flex;
    flex-direction: column;
    min-width: 0;
    position: relative;
}

.cx-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 0.6rem;
    padding: 0.4rem 0.5rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    margin-bottom: 0.4rem;
    flex-wrap: wrap;
    box-shadow: var(--cx-shadow-sm);
}

/* Canvas and the properties panel both sit in this one grid cell
   (grid-area: stage on each) so they overlap instead of stacking — the
   properties panel floats over the canvas as a compact box in the corner
   instead of reserving a permanent sidebar column, so it costs no width
   at all when there's nothing selected. */
.cx-canvas-stage {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-areas: "stage";
}

.cx-canvas-stage > .cx-canvas-wrap,
.cx-canvas-stage > .cx-properties-panel {
    grid-area: stage;
}

/* Toolbar tools cluster into labelled groups (Draw/Assets/Comic/Text/
   Layout/Remove) instead of one flat row — same buttons, same ids/data
   attributes, just wrapped for scannability. Each group's label sits in
   small caps above its button row (not inline beside it) to keep every
   group as narrow as its buttons, not its label text. */
.cx-tool-group {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
    padding-right: 0.6rem;
    margin-right: 0.1rem;
    border-right: 1px solid var(--cx-border);
}

.cx-tool-group:last-of-type {
    border-right: none;
    padding-right: 0;
    margin-right: 0;
}

.cx-tool-group-danger {
    margin-left: auto;
}

.cx-tool-group-label {
    font-size: 0.62rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--cx-text-muted);
    line-height: 1;
    white-space: nowrap;
}

.cx-tool-group-buttons {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.cx-zoom-controls {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    margin-left: auto;
    padding-left: 0.6rem;
    border-left: 1px solid var(--cx-border);
}

.cx-tool-group-danger + .cx-zoom-controls {
    margin-left: 0;
}

.cx-zoom-value {
    min-width: 3.4rem;
    padding: 0 0.2rem;
    border: none;
    background: transparent;
    color: var(--cx-text-muted);
    font-size: 0.78rem;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    border-radius: var(--cx-radius-sm);
}

.cx-zoom-value:hover {
    background: var(--cx-bg);
    color: var(--cx-text);
}

/* ---------- Properties panel (right column) ---------- */
/* Quick actions (Duplicate/Flip/Delete) only now — advanced styling
   (.cx-tool-attributes) moved back out to its own floating box near the
   canvas, matching com_storyboard; see the rule further down. */

.cx-properties-panel {
    align-self: start;
    justify-self: end;
    z-index: 20;
    margin: 0.6rem 0.6rem 0 0;
    width: 220px;
    max-width: calc(100% - 1.2rem);
    max-height: calc(100% - 1.2rem);
    overflow-y: auto;
    padding: 0.6rem 0.7rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    box-shadow: var(--cx-shadow-md);
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.cx-properties-empty {
    margin: 0;
    color: var(--cx-text-muted);
    font-size: 0.82rem;
    line-height: 1.4;
}

.cx-properties-empty[hidden] {
    display: none;
}

.cx-properties-quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.cx-properties-quick-actions[hidden] {
    display: none;
}

.cx-properties-quick-actions button {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.55rem;
    font-size: 0.78rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-text);
    cursor: pointer;
}

.cx-properties-quick-actions button:hover {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
    background: var(--cx-accent-soft);
}

.cx-properties-quick-actions #cx-qa-delete:hover {
    border-color: var(--cx-danger);
    color: var(--cx-danger);
    background: var(--cx-danger-soft);
}

/* Floating box, positioned by positionToolAttributes() in editor.js next
   to the active tool button or the current selection — matches
   com_storyboard's #cs-tool-attributes exactly (this used to be a
   persistent, always-in-flow column instead — see the Properties panel
   comment above). .cx-canvas-area is its positioning anchor (position:
   relative), same as com_storyboard's .cs-canvas-area. */
.cx-tool-attributes {
    position: absolute;
    z-index: 45;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    max-width: calc(100% - 1rem);
    padding: 0.35rem 0.5rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    box-shadow: var(--cx-shadow-md);
}

.cx-tool-attributes[hidden],
.cx-tool-attributes:empty,
.cx-tool-attributes.is-empty {
    display: none;
}

.cx-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
    background: transparent;
    color: var(--cx-text);
    border-radius: var(--cx-radius-sm);
    width: 34px;
    height: 34px;
    cursor: pointer;
    font-size: 0.95rem;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.cx-tool:hover {
    background: var(--cx-bg);
}

.cx-tool.is-active {
    border-color: var(--cx-accent);
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-tool-danger { color: var(--cx-danger); }
.cx-tool-danger:hover { background: var(--cx-danger-soft); }

.cx-tool-option {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    color: var(--cx-text-muted);
    margin-left: 0.5rem;
    padding-left: 0.5rem;
    border-left: 1px solid var(--cx-border);
    white-space: nowrap;
}

/* "Font Size" plus its number input, kept from wrapping onto two lines —
   the spinner-arrowed native number input is wide enough relative to the
   floating attributes box that it could otherwise drop below its label. */
#cx-font-size {
    width: 3.2rem;
    flex-shrink: 0;
    padding: 0.1rem 0.25rem;
    height: 1.6rem;
}

/* Equal specificity to the rule above, so this must come after it to win —
   without this, the `hidden` attribute (used to show/hide per-tool
   attributes) would be silently overridden by display:flex. */
.cx-tool-option[hidden] {
    display: none;
}

.cx-tool-option input[type="color"] {
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--cx-border-strong);
    border-radius: 6px;
    cursor: pointer;
}

/* ---------- Right-click context menu ---------- */
/* position:fixed + clientX/clientY (not document-relative coordinates) —
   see wireContextMenu() in editor.js, which positions this directly from
   the contextmenu event. */

.cx-context-menu {
    position: fixed;
    z-index: 1300;
    min-width: 180px;
    padding: 0.3rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-md);
}

.cx-context-menu[hidden] {
    display: none;
}

.cx-context-menu-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: none;
    border-radius: var(--cx-radius-sm);
    background: transparent;
    color: var(--cx-text);
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}

.cx-context-menu-item i {
    width: 1rem;
    text-align: center;
    color: var(--cx-text-muted);
    flex-shrink: 0;
}

.cx-context-menu-item:hover {
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-context-menu-item:hover i {
    color: var(--cx-accent-dark);
}

/* ---------- Toolbar popover (Annotate picker) ---------- */
/* Same open/close mechanics as the colour wheel popover — a toggled
   .cx-tool that reveals an absolutely-positioned panel of options. */

.cx-tool-popover-wrap {
    position: relative;
    display: inline-flex;
}

.cx-tool-popover {
    position: absolute;
    z-index: 50;
    top: calc(100% + 0.5rem);
    left: 0;
    width: 450px;
    max-width: calc(100vw - 2rem);
    max-height: 75vh;
    overflow-y: auto;
    padding: 0.6rem;
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.3rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-md);
}

.cx-tool-popover[hidden] {
    display: none;
}

/* ---------- Assets panel ---------- */
/* Same popover mechanics as .cx-tool-popover (toggled by the "Assets"
   toolbar button, closes on outside click via ComComic StripPopovers) but
   taller/wider — it needs room for a search box, category chips, and a
   scrollable thumbnail grid, not just a short list of options. */

.cx-asset-panel {
    position: absolute;
    z-index: 50;
    top: calc(100% + 0.5rem);
    left: 0;
    width: 450px;
    max-width: calc(100vw - 2rem);
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-md);
    overflow: hidden;
}

.cx-asset-panel[hidden] {
    display: none;
}

/* Library / Characters tabs — .cx-asset-panel-section holds whichever one
   is showing; both are flex columns themselves so their own search box /
   category chips / scrollable grid children keep behaving exactly as
   before, just one level deeper than when the panel had only one tab. */
.cx-asset-panel-tabs {
    flex: 0 0 auto;
    display: flex;
    border-bottom: 1px solid var(--cx-border);
}

.cx-asset-panel-tab {
    flex: 1;
    padding: 0.55rem;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--cx-text-muted);
    font-size: 0.78rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
}

.cx-asset-panel-tab:hover {
    color: var(--cx-text);
}

.cx-asset-panel-tab.is-active {
    color: var(--cx-accent-dark);
    border-bottom-color: var(--cx-accent);
}

.cx-asset-panel-section {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.cx-asset-panel-section[hidden] {
    display: none;
}

/* ---------- Characters tab ---------- */
/* A student's own reusable images for this comic — placed via a plain
   fabric.Image (see placeCharacterOnCanvas() in editor.js), not the SVG
   pipeline the Library tab above uses. */

.cx-character-grid {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0.6rem;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
    gap: 0.6rem;
    align-content: start;
}

.cx-character-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
}

.cx-character-thumb {
    width: 100%;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-bg);
    color: var(--cx-text-muted);
    font-size: 1.4rem;
    cursor: pointer;
    overflow: hidden;
}

.cx-character-thumb:hover {
    border-color: var(--cx-accent);
}

.cx-character-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cx-character-name {
    width: 100%;
    font-size: 0.72rem;
    color: var(--cx-text);
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    border-radius: var(--cx-radius-sm);
    padding: 0.1rem 0.3rem;
}

.cx-character-name:hover {
    background: var(--cx-bg);
}

.cx-character-name-input {
    width: 100%;
    font-size: 0.72rem;
    text-align: center;
    padding: 0.1rem 0.3rem;
    border: 1px solid var(--cx-accent);
    border-radius: var(--cx-radius-sm);
    font-family: inherit;
}

.cx-character-delete {
    position: absolute;
    top: -5px;
    right: -5px;
    width: 18px;
    height: 18px;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: var(--cx-danger);
    color: #fff;
    font-size: 0.6rem;
    cursor: pointer;
    z-index: 1;
}

.cx-character-card:hover .cx-character-delete {
    display: flex;
}

.cx-character-add {
    flex: 0 0 auto;
    margin: 0.6rem;
    width: auto;
}

.cx-asset-panel-search {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.6rem;
    border-bottom: 1px solid var(--cx-border);
    color: var(--cx-text-muted);
}

.cx-asset-panel-search input {
    flex: 1;
    min-width: 0;
    height: 32px;
    padding: 0.2rem 0.5rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.85rem;
    background: var(--cx-bg);
    color: var(--cx-text);
}

.cx-asset-panel-search input:focus {
    outline: none;
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}

.cx-asset-panel-categories {
    flex: 0 0 auto;
    display: flex;
    gap: 0.3rem;
    padding: 0.5rem 0.6rem;
    overflow-x: auto;
    border-bottom: 1px solid var(--cx-border);
}

.cx-asset-category {
    flex-shrink: 0;
    padding: 0.25rem 0.65rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: 999px;
    background: var(--cx-surface);
    color: var(--cx-text-muted);
    font-size: 0.72rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.cx-asset-category:hover {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
}

.cx-asset-category.is-active {
    background: var(--cx-accent);
    border-color: var(--cx-accent);
    color: #fff;
}

.cx-asset-panel-grid {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0.6rem;
}

.cx-asset-group-title {
    margin: 0.6rem 0 0.4rem;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cx-text-muted);
}

.cx-asset-group-title:first-child {
    margin-top: 0;
}

.cx-asset-group {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.5rem;
}

.cx-asset-thumb {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    padding: 0.4rem 0.3rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-bg);
    cursor: grab;
    text-align: center;
}

.cx-asset-thumb:hover {
    border-color: var(--cx-accent);
    background: var(--cx-accent-soft);
}

.cx-asset-thumb img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    padding: 0.3rem;
    pointer-events: none;
}

.cx-asset-thumb-name {
    font-size: 0.68rem;
    color: var(--cx-text-muted);
    line-height: 1.2;
}

.cx-asset-thumb.is-premium {
    cursor: pointer;
}

.cx-asset-thumb.is-premium img {
    opacity: 0.45;
}

.cx-asset-lock {
    position: absolute;
    left: 0.2rem;
    right: 0.2rem;
    bottom: 0.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    padding: 0.15rem 0.3rem;
    border-radius: var(--cx-radius-sm);
    background: var(--cx-warning);
    color: #fff;
    font-size: 0.55rem;
    font-weight: 700;
    line-height: 1.15;
    text-align: center;
}

/* Same premium-lock badge as .cx-asset-lock above, but inline within a
   .cx-tool-popover-option row (the Shapes popover) rather than overlaid on
   a thumbnail. */
.cx-shape-lock {
    flex-shrink: 0;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.15rem 0.4rem;
    border-radius: var(--cx-radius-sm);
    background: var(--cx-warning);
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 1.2;
}

.cx-asset-empty {
    margin: 1rem 0;
    text-align: center;
    color: var(--cx-text-muted);
    font-size: 0.85rem;
}

.cx-tool-popover-toggle {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.4rem;
    font-size: 0.82rem;
    color: var(--cx-text);
    border-bottom: 1px solid var(--cx-border);
    margin-bottom: 0.2rem;
}

.cx-tool-popover-hint {
    margin: 0;
    color: var(--cx-text-muted);
    line-height: 1.35;
}

.cx-tool-popover-option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.4rem;
    border: 1px solid transparent;
    border-radius: var(--cx-radius-sm);
    background: transparent;
    text-align: left;
    cursor: pointer;
    font-family: inherit;
}

.cx-tool-popover-option:hover {
    border-color: var(--cx-accent);
    background: var(--cx-accent-soft);
}

.cx-tool-popover-swatch {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    color: #fff;
    font-size: 0.8rem;
}

.cx-tool-popover-text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.cx-tool-popover-title {
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--cx-text);
}

.cx-tool-popover-desc {
    font-size: 0.75rem;
    color: var(--cx-text-muted);
    line-height: 1.3;
}

/* Shape options have no per-shape colour the way bubble types do (they're
   drawn in whatever the Colour/Fill pickers are currently set to) — one
   flat accent background for every swatch instead. */
.cx-shape-option .cx-tool-popover-swatch {
    background: var(--cx-accent);
}

.cx-canvas-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--cx-bg);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    overflow: auto;
}

.cx-canvas-wrap canvas {
    box-shadow: var(--cx-shadow-md);
    border-radius: 4px;
}

.cx-feedback-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-height: 50vh;
    overflow-y: auto;
}

.cx-feedback-item {
    background: var(--cx-bg);
    border-radius: var(--cx-radius-sm);
    padding: 0.5rem 0.65rem;
    font-size: 0.85rem;
}

.cx-feedback-item p {
    margin: 0.3rem 0 0;
}

/* Which panel a comment is about (or "Whole comic strip") — every comment
   shows this now, since the list is no longer filtered down to just the
   currently open panel (see loadComments() in editor.js/comicpage.js). A
   panel-specific tag is a real button (jumps to that panel); "Whole
   comic strip"/deleted-panel ones are plain, non-interactive text. */
.cx-feedback-scope {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
    font-size: 0.7rem;
    font-weight: 700;
    border: none;
    cursor: pointer;
}

button.cx-feedback-scope:hover {
    background: var(--cx-accent);
    color: #fff;
}

.cx-feedback-scope-general {
    background: var(--cx-bg);
    color: var(--cx-text-muted);
    border: 1px solid var(--cx-border);
    cursor: default;
}

.cx-feedback-author {
    font-weight: 600;
    margin-right: 0.4rem;
    color: var(--cx-accent-dark);
}

.cx-feedback-item time {
    color: var(--cx-text-muted);
    font-size: 0.72rem;
}

.cx-feedback-read {
    display: block;
    margin-top: 0.4rem;
    color: var(--cx-text-muted);
    font-size: 0.72rem;
    font-style: italic;
}

.cx-feedback-ack {
    display: inline-flex;
    margin-top: 0.4rem;
    padding: 0.25rem 0.55rem;
    font-size: 0.72rem;
}

.cx-comment-form {
    margin-top: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.cx-comment-scope-hint {
    margin: 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--cx-accent-dark);
}

.cx-comment-form textarea {
    width: 100%;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    padding: 0.5rem 0.6rem;
    font-family: inherit;
    font-size: 0.88rem;
    resize: none;
    overflow-y: auto;
    max-height: 12rem;
}

.cx-comment-form textarea:focus {
    outline: none;
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}

@media (max-width: 900px) {
    /* The editor no longer fits in a fixed viewport-height box once its
       three columns stack into rows below — let the page grow and scroll
       naturally instead of clipping the meta panel/filmstrip. */
    .com-comicstrip-editor {
        height: auto;
        min-height: 0;
    }

    .cx-editor-body {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto;
        height: auto;
    }

    .cx-filmstrip {
        display: flex;
        border-right: none;
        border-bottom: 1px solid var(--cx-border);
        padding-right: 0;
        padding-bottom: 0.6rem;
    }

    .cx-filmstrip-list {
        flex-direction: row;
        overflow-x: auto;
    }

    .cx-filmstrip-item {
        min-width: 90px;
    }

    .cx-tool-group {
        border-right: none;
        padding-right: 0;
        margin-right: 0;
    }

    .cx-tool-group-danger,
    .cx-zoom-controls {
        margin-left: 0;
    }

    .cx-zoom-controls {
        padding-left: 0;
        border-left: none;
    }

    .cx-guest-cta {
        margin: 1.5rem auto;
        padding: 1.75rem 1.25rem;
    }

    .cx-guest-cta-actions {
        flex-direction: column;
    }

    /* Header bars: title/actions no longer fit on one line once the
       viewport narrows — wrap onto their own rows instead of squeezing or
       overflowing. */
    .cx-editor-header {
        flex-wrap: wrap;
    }

    .cx-editor-title-group {
        flex-basis: 100%;
        order: -1;
    }

    .cx-editor-status {
        min-width: 0;
    }

}

@media (max-width: 600px) {
    .cx-editor-header {
        padding: 0.3rem 0;
    }

    .cx-editor-actions {
        flex-wrap: wrap;
        row-gap: 0.4rem;
    }

    .cx-btn-back {
        margin-left: 0;
        padding-left: 0.9rem;
    }

    .cx-toolbar {
        gap: 0.25rem;
    }

    .cx-tool-group {
        gap: 0.2rem;
    }

    .cx-tool-option {
        margin-left: 0.25rem;
        padding-left: 0.35rem;
    }

    .cx-sfx-popover {
        width: min(260px, calc(100vw - 3rem));
    }

    .cx-modal {
        padding: 0.75rem;
    }

    .cx-modal-dialog {
        max-height: min(88vh, 720px);
    }

    .cx-modal-header {
        flex-wrap: wrap;
        padding: 0.85rem 1rem;
    }

    .cx-color-popover {
        width: min(220px, calc(100vw - 3rem));
    }

    .cx-tool-popover {
        width: min(280px, calc(100vw - 3rem));
        grid-template-columns: 1fr;
    }

    .cx-toast-container {
        left: 0.75rem;
        right: 0.75rem;
        top: 0.75rem;
        transform: none;
        max-width: none;
    }

    .cx-arrange-layout {
        flex-direction: column;
    }

    .cx-arrange-sidebar {
        width: 100%;
        flex-basis: auto;
    }

    .cx-arrange-grid {
        grid-template-columns: repeat(2, 1fr);
        grid-auto-rows: 90px;
    }
}

/* ---------- Toast notifications ---------- */
/* Not scoped under .com-comicstrip — toast.js appends this directly to
   <body>, same reasoning as .cx-page-loader (see above): it needs to sit
   above everything regardless of which component view is currently
   rendering .com-comicstrip's own markup. */

.cx-toast-container {
    position: fixed;
    z-index: 1400;
    top: 1.25rem;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: min(360px, calc(100vw - 2rem));
}

.cx-toast {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    padding: 0.7rem 0.8rem;
    background: #1c2130;
    color: #fff;
    border-radius: 10px;
    box-shadow: 0 6px 20px rgba(17, 24, 39, 0.25);
    font-size: 0.85rem;
    line-height: 1.35;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.cx-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}

.cx-toast i.fa-circle-check { color: #4ade80; }
.cx-toast i.fa-circle-exclamation { color: #f87171; }
.cx-toast i.fa-circle-info { color: #93c5fd; }

.cx-toast > i {
    margin-top: 0.15rem;
    flex-shrink: 0;
}

.cx-toast > span {
    flex: 1;
    min-width: 0;
    word-break: break-word;
}

.cx-toast-close {
    flex-shrink: 0;
    border: none;
    background: transparent;
    color: rgba(255, 255, 255, 0.6);
    cursor: pointer;
    padding: 0;
    line-height: 1;
    font-size: 0.85rem;
}

.cx-toast-close:hover {
    color: #fff;
}

/* ---------- Dashboard: modals (comic strip preview + recycle bin) ---------- */
/* Generic modal chrome shared by every dashboard modal — the preview modal
   and the recycle bin modal both use these same classes for the overlay,
   dialog frame, header, and close button, and each adds their own
   *-body-specific classes for their actual content. */

body.cx-modal-open {
    overflow: hidden;
}

.cx-modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.cx-modal[hidden] {
    display: none;
}

.cx-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(17, 24, 39, 0.55);
}

.cx-modal-dialog {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 860px;
    max-height: min(80vh, 720px);
    background: var(--cx-surface);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-md);
    overflow: hidden;
}

.cx-modal-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--cx-border);
}

.cx-modal-header h2 {
    flex: 1;
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--cx-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cx-modal-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--cx-text-muted);
    font-size: 1.1rem;
    cursor: pointer;
    border-radius: var(--cx-radius-sm);
}

.cx-modal-close:hover {
    background: var(--cx-bg);
    color: var(--cx-text);
}

.cx-modal-body {
    flex: 1;
    overflow-y: auto;
    padding: 1.25rem;
}

/* ---------- Colour wheel picker ---------- */
/* Markup/behaviour built by color-wheel.js — see that file's build()
   function for the exact structure this styles. The native <input
   type="color"> stays in the DOM (hidden) so existing $('#cx-color').val()
   reads/writes elsewhere in editor.js/sandbox.js keep working unchanged. */

.cx-color-picker {
    position: relative;
    display: inline-block;
}

.cx-color-swatch {
    display: inline-flex;
    width: 2.1rem;
    height: 2.1rem;
    padding: 0;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    cursor: pointer;
    background-color: #000;
    box-shadow: var(--cx-shadow-sm);
}

.cx-color-swatch:hover {
    border-color: var(--cx-accent);
}

.cx-color-popover {
    position: absolute;
    z-index: 50;
    top: calc(100% + 0.5rem);
    left: 0;
    width: 220px;
    padding: 0.85rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-md);
}

.cx-color-popover[hidden] {
    display: none;
}

.cx-color-wheel-wrap {
    position: relative;
    margin: 0 auto 0.6rem;
    /* width/height are set inline by color-wheel.js to fit exactly the
       generated hex grid — this only centres that box within the popover. */
}

.cx-color-hex-swatch {
    position: absolute;
    padding: 0;
    border: none;
    cursor: pointer;
    clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
    transition: transform 0.1s ease, filter 0.1s ease;
}

.cx-color-hex-swatch:hover {
    filter: brightness(1.08);
    transform: scale(1.15);
    z-index: 2;
}

.cx-color-hex-swatch.is-selected {
    filter: brightness(1.08);
    transform: scale(1.2);
    z-index: 3;
}

.cx-color-tones-label {
    display: block;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--cx-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.3rem;
}

.cx-color-tones {
    display: flex;
    gap: 0.3rem;
    margin-bottom: 0.7rem;
}

.cx-color-tone {
    flex: 1;
    aspect-ratio: 1 / 1;
    padding: 0;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    cursor: pointer;
}

.cx-color-tone:hover {
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 2px var(--cx-accent-soft);
}

.cx-color-fields {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.cx-color-fields > label,
.cx-color-rgb label {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--cx-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.cx-color-rgb label {
    text-align: center;
}

.cx-color-fields input[type="text"] {
    width: 100%;
    padding: 0.2rem 0.35rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.82rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    text-align: center;
}

.cx-color-rgb {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.3rem;
}

.cx-color-rgb input[type="number"] {
    width: 100%;
    padding: 0.2rem 0.2rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.8rem;
    text-align: center;
}

.cx-color-fields input:focus-visible {
    outline: 2px solid var(--cx-accent);
    outline-offset: 1px;
}

/* ---------- Panel size presets popover ---------- */
/* Toggled by #cx-tool-panel-size, same open/close mechanics as every other
   toolbar popover (see .cx-tool-popover above) — a 2x2 grid of the four
   presets defined by PanelSizeHelper::PRESETS, each button showing a
   proportional preview box so the shape is readable at a glance rather
   than only from its label. */

.cx-panel-size-popover {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 60;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.4rem;
    width: 240px;
    padding: 0.6rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    background: var(--cx-surface);
    box-shadow: var(--cx-shadow-md);
}

.cx-panel-size-popover[hidden] {
    display: none;
}

.cx-panel-size-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    padding: 0.5rem 0.35rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-text);
    font-size: 0.72rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.cx-panel-size-option:hover {
    background: var(--cx-accent-soft);
    border-color: var(--cx-accent);
}

.cx-panel-size-option.is-active {
    background: var(--cx-accent-soft);
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
}

/* Proportional preview of the preset's aspect ratio — sized in JS-free CSS
   from the data-size attribute so it stays correct without a second copy of
   the preset dimensions in this file. */
.cx-panel-size-shape {
    display: block;
    border: 2px solid currentColor;
    border-radius: 3px;
    background: var(--cx-bg);
}

.cx-panel-size-option[data-size="square"] .cx-panel-size-shape { width: 26px; height: 26px; }
.cx-panel-size-option[data-size="wide"] .cx-panel-size-shape { width: 40px; height: 20px; }
.cx-panel-size-option[data-size="tall"] .cx-panel-size-shape { width: 20px; height: 40px; }
.cx-panel-size-option[data-size="large"] .cx-panel-size-shape { width: 38px; height: 38px; }

/* ---------- Arrange-panels modal ---------- */
/* A sidebar (whichever panel is selected — size/scene/dialogue, all
   editable) next to a main grid showing every panel laid out spatially
   the same way the real comic page renders them: see .cx-page-grid
   further down, whose --cx-cell-size/grid-auto-flow: dense/per-size
   grid-column+grid-row span pattern this deliberately mirrors, so this
   grid doubles as a true layout preview and not just a plain list.
   Built by renderArrangeGrid()/renderArrangeSidebar() in editor.js. */

.cx-modal-dialog-wide {
    max-width: 1080px;
}

.cx-arrange-hint {
    margin: 0 0 1rem;
    color: var(--cx-text-muted);
    font-size: 0.85rem;
}

.cx-arrange-layout {
    display: flex;
    align-items: flex-start;
    gap: 1.25rem;
}

.cx-arrange-sidebar {
    flex: 0 0 240px;
    width: 240px;
    padding: 0.9rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    background: var(--cx-bg);
}

.cx-arrange-sidebar-empty {
    margin: 0;
    color: var(--cx-text-muted);
    font-size: 0.85rem;
}

.cx-arrange-sidebar-title {
    margin: 0 0 0.75rem;
    font-size: 0.95rem;
    font-weight: 700;
}

.cx-arrange-field {
    margin-bottom: 0.9rem;
}

.cx-arrange-field:last-child {
    margin-bottom: 0;
}

.cx-arrange-field-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--cx-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.4rem;
}

.cx-arrange-field textarea {
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-family: inherit;
    font-size: 0.85rem;
    resize: vertical;
}

.cx-arrange-sidebar-size-row {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.4rem;
}

.cx-arrange-sidebar-size-row .cx-panel-size-option {
    width: 100%;
}

.cx-arrange-main {
    flex: 1;
    min-width: 0;
    overflow-x: auto;
}

.cx-arrange-add-panel {
    margin-bottom: 0.75rem;
}

.cx-arrange-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 110px;
    grid-auto-flow: dense;
    gap: 0.7rem;
}

.cx-arrange-card {
    position: relative;
    border: 2px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    overflow: hidden;
    cursor: grab;
    background: var(--cx-bg);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.cx-arrange-card:hover {
    box-shadow: var(--cx-shadow-sm);
}

.cx-arrange-card.is-selected {
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}

.cx-arrange-card.ui-sortable-helper {
    box-shadow: var(--cx-shadow-md);
}

.cx-arrange-card[data-size="square"] { grid-column: span 1; grid-row: span 1; }
.cx-arrange-card[data-size="wide"]   { grid-column: span 2; grid-row: span 1; }
.cx-arrange-card[data-size="tall"]   { grid-column: span 1; grid-row: span 2; }
.cx-arrange-card[data-size="large"]  { grid-column: span 2; grid-row: span 2; }

.cx-arrange-thumb {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--cx-text-muted);
    font-weight: 600;
}

.cx-arrange-thumb img { width: 100%; height: 100%; object-fit: cover; }

.cx-arrange-number {
    position: absolute;
    top: 4px;
    left: 4px;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: rgba(17, 24, 39, 0.65);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 600;
}

/* Duplicate/delete on hover — reuses .cx-filmstrip-actions verbatim (same
   corner-badge treatment), just also triggered by hovering an arrange
   card, not only a filmstrip item. Used on the Planning page's grid. */
.cx-arrange-card:hover .cx-filmstrip-actions {
    display: flex;
}

/* ---------- Speech-bubble popover ---------- */
/* Reuses .cx-tool-popover-option rows for the four bubble types; the corner
   picker below them is shown by editor.js only for the types that actually
   draw a tail (dialogue/thought). */

.cx-bubble-corner-block {
    grid-column: 1 / -1;
    margin-top: 0.35rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--cx-border);
}

.cx-bubble-corner-block[hidden] {
    display: none;
}

.cx-bubble-corner-label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--cx-text-muted);
}

.cx-bubble-corner-picker {
    display: grid;
    grid-template-columns: repeat(2, 24px);
    grid-template-rows: repeat(2, 24px);
    gap: 0.35rem;
    justify-content: start;
}

.cx-bubble-corner {
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-text-muted);
    cursor: pointer;
    position: relative;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

/* A small filled triangle in the corner the tail will point out of — the
   button's own position in the 2x2 grid says which corner it is, this makes
   it readable without relying on that alone. */
.cx-bubble-corner::after {
    content: "";
    position: absolute;
    width: 0;
    height: 0;
    border: 5px solid transparent;
}

.cx-bubble-corner[data-corner="top-left"]::after { top: 3px; left: 3px; border-top-color: currentColor; border-left-color: currentColor; }
.cx-bubble-corner[data-corner="top-right"]::after { top: 3px; right: 3px; border-top-color: currentColor; border-right-color: currentColor; }
.cx-bubble-corner[data-corner="bottom-left"]::after { bottom: 3px; left: 3px; border-bottom-color: currentColor; border-left-color: currentColor; }
.cx-bubble-corner[data-corner="bottom-right"]::after { bottom: 3px; right: 3px; border-bottom-color: currentColor; border-right-color: currentColor; }

.cx-bubble-corner:hover {
    background: var(--cx-accent-soft);
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
}

.cx-bubble-corner.is-active {
    background: var(--cx-accent-soft);
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
}

/* The bubble popover holds a single column of type rows plus the corner
   block. Widened past the base .cx-tool-popover size (450px) because the
   Dialogue/Thought descriptions were wrapping to a second line at 500px,
   pushing total content past max-height and forcing a scrollbar even
   though only two options plus the tail-position picker need to fit. */
.cx-bubble-popover {
    grid-template-columns: 1fr;
    width: 600px;
}

/* ---------- Sound-effect popover ---------- */
/* A grid of preset words (see SFX_PRESETS in editor.js) plus a custom-text
   fallback — overrides .cx-tool-popover's default single-column grid since
   short preset words read better two-per-row. */

.cx-sfx-popover {
    width: 260px;
    grid-template-columns: 1fr;
}

.cx-sfx-preset-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.4rem;
}

.cx-sfx-preset {
    padding: 0.5rem 0.4rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-bg);
    color: var(--cx-text);
    font-family: inherit;
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    text-align: center;
}

.cx-sfx-preset:hover {
    border-color: var(--cx-accent);
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-sfx-custom-row {
    grid-column: 1 / -1;
    display: flex;
    gap: 0.4rem;
    margin-top: 0.35rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--cx-border);
}

.cx-sfx-custom-row input {
    flex: 1;
    min-width: 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-family: inherit;
    font-size: 0.85rem;
}

/* ---------- Dashboard ---------- */

.cx-dashboard-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
}

.cx-dashboard-header-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

#cx-recycle-bin-toggle {
    position: relative;
}

.cx-recycle-bin-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.35rem;
    margin-left: 0.35rem;
    border-radius: 999px;
    background: var(--cx-danger);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
}

.cx-comic-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1.25rem;
}

.cx-comic-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-sm);
    overflow: hidden;
    transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.cx-comic-card:hover {
    box-shadow: var(--cx-shadow-md);
    transform: translateY(-2px);
}

.cx-comic-card .cx-review-badge {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    z-index: 1;
}

.cx-comic-card-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 4 / 3;
    background: var(--cx-bg);
    color: var(--cx-text-muted);
    font-size: 2rem;
    overflow: hidden;
}

.cx-comic-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cx-comic-card-title {
    display: block;
    padding: 0.75rem 0.85rem 0.15rem;
    font-weight: 600;
    color: var(--cx-text);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cx-comic-card-title:hover {
    color: var(--cx-accent);
}

.cx-comic-card-title .cx-feedback-indicator {
    margin-left: 0.3rem;
    color: var(--cx-accent);
}

.cx-comic-card-meta {
    padding: 0 0.85rem 0.75rem;
    font-size: 0.8rem;
    color: var(--cx-text-muted);
}

/* Open gets its own row (flex-basis: 100%) since the six actions together
   never fit on one line at the card's minmax(220px, ...) width — the icon
   buttons were getting squeezed out of view. The rest wrap to a second row
   and share it evenly. */
.cx-comic-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0 0.75rem 0.75rem;
    margin-top: auto;
}

.cx-comic-card-actions .cx-btn {
    flex: 1;
    justify-content: center;
    padding: 0.4rem 0.5rem;
}

.cx-comic-card-actions .cx-btn:first-child {
    flex: 1 1 100%;
}

/* ---------- Dashboard: quick-preview modal (card grid inside a modal,
   same panel-size language as the full Comic Page grid) ---------- */

.cx-preview-modal-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.6rem;
}

.cx-preview-modal-panel {
    position: relative;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    overflow: hidden;
    background: var(--cx-bg);
}

.cx-preview-modal-panel[data-size="square"] { grid-column: span 1; grid-row: span 1; }
.cx-preview-modal-panel[data-size="wide"]   { grid-column: span 2; grid-row: span 1; }
.cx-preview-modal-panel[data-size="tall"]   { grid-column: span 1; grid-row: span 2; }
.cx-preview-modal-panel[data-size="large"]  { grid-column: span 2; grid-row: span 2; }

.cx-preview-modal-panel-thumb {
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--cx-text-muted);
}

.cx-preview-modal-panel-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.cx-preview-modal-panel-meta {
    position: absolute;
    top: 0.3rem;
    left: 0.3rem;
}

.cx-preview-modal-panel-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: rgba(28, 33, 48, 0.65);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
}

/* ---------- Recycle bin list ---------- */

.cx-modal-empty,
.cx-modal-loading {
    text-align: center;
    color: var(--cx-text-muted);
    padding: 1.5rem 0;
}

.cx-modal-error {
    text-align: center;
    color: var(--cx-danger);
    padding: 1.5rem 0;
}

.cx-recycle-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.cx-recycle-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-bg);
}

.cx-recycle-item-info {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.cx-recycle-item-title {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cx-recycle-item-meta {
    font-size: 0.78rem;
    color: var(--cx-text-muted);
}

.cx-recycle-item-actions {
    display: flex;
    gap: 0.4rem;
    flex-shrink: 0;
}

/* ---------- Teacher navigation ---------- */

.cx-breadcrumb {
    font-size: 0.85rem;
    color: var(--cx-text-muted);
    margin-bottom: 0.5rem;
}

.cx-list-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.5rem;
}

.cx-list-table th,
.cx-list-table td {
    text-align: left;
    padding: 0.5rem 0.6rem;
    border-bottom: 1px solid var(--cx-border);
    font-size: 0.9rem;
}

.cx-list-table th {
    color: var(--cx-text-muted);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

/* ---------- Teacher dashboard accordion ---------- */

.cx-teacher-accordion {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.cx-teacher-class,
.cx-teacher-student {
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
}

.cx-teacher-student {
    margin: 0.5rem 0;
    background: var(--cx-bg);
}

.cx-teacher-class > summary,
.cx-teacher-student > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.65rem 0.85rem;
    cursor: pointer;
    list-style: none;
    font-weight: 600;
}

.cx-teacher-class > summary::-webkit-details-marker,
.cx-teacher-student > summary::-webkit-details-marker {
    display: none;
}

.cx-teacher-class[open] > summary,
.cx-teacher-student[open] > summary {
    border-bottom: 1px solid var(--cx-border);
}

.cx-teacher-item-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cx-teacher-count-badge {
    flex-shrink: 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--cx-text-muted);
    background: var(--cx-bg);
    border-radius: 999px;
    padding: 0.15rem 0.6rem;
}

.cx-teacher-class-body {
    padding: 0.75rem 0.85rem;
}

.cx-teacher-student-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* ---------- Comic Page (layout / print / review) ---------- */

.cx-comic-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: 1.5rem 1rem 3rem;
}

.cx-page-exit {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-bottom: 1.25rem;
}

.cx-page-exit .cx-feedback-bell {
    margin-left: auto;
}

.cx-page-empty {
    text-align: center;
    color: var(--cx-text-muted);
    padding: 3rem 0;
}

.cx-print-pages {
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
}

.cx-print-page {
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    box-shadow: var(--cx-shadow-sm);
    padding: 1.25rem;
}

.cx-print-page-header {
    margin-bottom: 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 2px solid var(--cx-text);
}

.cx-print-page-header-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.cx-print-page-title {
    font-weight: 700;
    font-size: 1.1rem;
}

.cx-print-page-description {
    margin: 0.35rem 0 0;
    font-size: 0.85rem;
    color: var(--cx-text-muted);
}

.cx-print-page-header-meta,
.cx-print-page-name,
.cx-print-page-pageof {
    font-size: 0.8rem;
    color: var(--cx-text-muted);
}

/* The grid itself: 4 equal columns, square cells. Row height comes from
   --cx-cell-size, kept in sync with the actual rendered column width by a
   ResizeObserver in comicpage.js — a plain 1fr row would follow content
   height instead and rows/columns would drift out of being square as soon
   as a browser's viewport width changed. grid-auto-flow: dense lets a
   later Square panel backfill a gap a Wide/Tall one left behind. */
.cx-page-grid {
    --cx-cell-size: 120px;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: var(--cx-cell-size);
    grid-auto-flow: dense;
    gap: 10px;
}

.cx-panel-card {
    position: relative;
    border: 2px solid var(--cx-text);
    border-radius: var(--cx-radius-sm);
    overflow: hidden;
    cursor: pointer;
    background: var(--cx-bg);
    transition: box-shadow 0.15s ease;
}

.cx-panel-card:hover,
.cx-panel-card:focus-visible {
    box-shadow: 0 0 0 3px var(--cx-accent-soft), var(--cx-shadow-md);
}

.cx-panel-card[data-size="square"] { grid-column: span 1; grid-row: span 1; }
.cx-panel-card[data-size="wide"]   { grid-column: span 2; grid-row: span 1; }
.cx-panel-card[data-size="tall"]   { grid-column: span 1; grid-row: span 2; }
.cx-panel-card[data-size="large"]  { grid-column: span 2; grid-row: span 2; }

.cx-panel-card-media {
    position: relative;
    width: 100%;
    height: 100%;
}

.cx-panel-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.cx-panel-card-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--cx-text-muted);
    font-size: 1.5rem;
}

.cx-panel-card-number {
    position: absolute;
    top: 0.3rem;
    left: 0.3rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35rem;
    height: 1.35rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: rgba(28, 33, 48, 0.7);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 700;
}

.cx-panel-card-feedback-icon {
    display: none;
    position: absolute;
    top: 0.3rem;
    right: 0.3rem;
    z-index: 1;
    color: var(--cx-accent);
    background: #fff;
    border-radius: 50%;
    padding: 0.15rem;
    font-size: 0.85rem;
}

.cx-panel-card.has-feedback .cx-panel-card-feedback-icon {
    display: block;
}

/* Per-panel popup modal — reuses .cx-feedback-panel/.cx-feedback-list/
   .cx-comment-form from the shared feedback-modal rules above. */
.cx-panel-modal-dialog {
    max-width: 640px;
}

.cx-panel-modal-media {
    display: flex;
    justify-content: center;
    background: var(--cx-bg);
    border-radius: var(--cx-radius-sm);
    padding: 0.75rem;
    margin-bottom: 1rem;
}

.cx-panel-modal-media img {
    max-width: 100%;
    max-height: 50vh;
    border-radius: var(--cx-radius-sm);
}

/* Print footer — position:fixed so it repeats in the same corner of every
   printed page rather than only the last one. Screen-hidden; see the
   @media print block below. */
.cx-print-footer,
.cx-print-footer-text {
    display: none;
}

/* ---------- Print ---------- */

@media print {
    /* Print isolation: this page can now be mounted inside com_comicstrip's
       Teacher Dashboard (the class/student columns are real siblings there,
       not just template chrome outside this component's control like
       com_storyboard's case — see that component's identical comment) — so
       hide EVERYTHING on the page, then re-show only the comic page itself.
       Hidden elements still occupy their normal layout space (unlike
       display:none), which is exactly why .cx-comic-page is pulled out of
       flow with position:absolute below — that makes it render at the
       physical top of the page regardless of how much blank space its
       hidden siblings would otherwise reserve. Harmless on the standalone
       page too, which has no siblings to hide in the first place. */
    body * {
        visibility: hidden;
    }

    .cx-comic-page,
    .cx-comic-page * {
        visibility: visible;
    }

    .cx-comic-page {
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
    }

    .cx-page-exit,
    .cx-feedback-bell,
    .cx-modal {
        display: none !important;
    }

    .cx-comic-page {
        max-width: none;
        margin: 0;
        padding: 0;
    }

    .cx-print-pages {
        gap: 0;
    }

    .cx-print-page {
        box-shadow: none;
        border: none;
        border-radius: 0;
        break-after: page;
        page-break-after: always;
    }

    .cx-print-page:last-child {
        break-after: auto;
        page-break-after: auto;
    }

    .cx-panel-card {
        break-inside: avoid;
    }

    .cx-print-footer,
    .cx-print-footer-text {
        display: block;
        position: fixed;
        bottom: 0.4cm;
    }

    .cx-print-footer {
        left: 0.6cm;
    }

    .cx-print-footer-logo {
        height: 22px;
    }

    .cx-print-footer-text {
        right: 0.6cm;
        text-align: right;
        font-size: 0.65rem;
        color: var(--cx-text-muted);
    }
}

/* ---------- Reading direction (manga mirroring) ---------- */
/* Panel numbering always follows the stored reading order regardless — only
   the grid's visual flow mirrors. grid-auto-flow: dense (already set on
   .cx-page-grid) respects `direction`, so this is the only rule needed. */

.cx-comic-page[data-direction="manga"] .cx-page-grid {
    direction: rtl;
}

/* Panel numbers/feedback icons are corner-anchored in LTR logical terms
   (top/left, top/right) — under RTL those need to swap back to their
   intended screen corner rather than following the mirrored grid. */
.cx-comic-page[data-direction="manga"] .cx-panel-card {
    direction: ltr;
}

/* ---------- Story-planning strip (editor) ---------- */
/* The current panel's "What happens"/"Dialogue" text — editable here for
   the owning student (own autosave, folded into the same panel.save
   request as the drawing), read-only (via the textareas' own readonly
   attribute, and hidden entirely when both are empty) for a teacher/admin
   viewer. See renderPlanningStrip() in editor.js. */

.cx-planning-strip {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 0.75rem;
    padding: 0.6rem 0.85rem;
    background: var(--cx-accent-soft);
    border: 1px solid var(--cx-border);
    border-left: 3px solid var(--cx-accent);
    border-radius: var(--cx-radius-sm);
    font-size: 0.85rem;
}

.cx-planning-row {
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
}

.cx-planning-label {
    flex-shrink: 0;
    font-weight: 700;
    color: var(--cx-accent-dark);
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
    min-width: 5.5rem;
    padding-top: 0.3rem;
}

/* Auto-expanded by autoExpandTextarea() in editor.js (same technique the
   teacher comment box uses) rather than given a fixed rows= height, so a
   long planning note isn't clipped — resize:none since the height is
   already managed for it, overflow:hidden so the auto-grow doesn't flash a
   scrollbar for one frame before the height catches up. Transparent/
   borderless background: this already reads as an input by sitting inside
   the accent-tinted strip, and a full bordered box here would visually
   compete with the field's own label right next to it. */
.cx-planning-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    border-radius: var(--cx-radius-sm);
    padding: 0.25rem 0.35rem;
    font-family: inherit;
    font-size: 0.85rem;
    line-height: 1.35;
    color: var(--cx-text);
    resize: none;
    overflow: hidden;
}

.cx-planning-input:focus {
    outline: none;
    background: var(--cx-surface);
    box-shadow: 0 0 0 1px var(--cx-accent);
}

.cx-planning-input[readonly] {
    cursor: default;
}

/* ---------- Story-planning block (Comic Page panel popup) ---------- */

.cx-panel-modal-planning {
    margin: 0 0 1rem;
    padding: 0.6rem 0.85rem;
    background: var(--cx-bg);
    border-left: 3px solid var(--cx-accent);
    border-radius: var(--cx-radius-sm);
}

.cx-panel-modal-planning-row {
    margin-bottom: 0.5rem;
}

.cx-panel-modal-planning-row:last-child {
    margin-bottom: 0;
}

.cx-panel-modal-planning-label {
    display: block;
    font-weight: 700;
    color: var(--cx-accent-dark);
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
    margin-bottom: 0.15rem;
}

.cx-panel-modal-planning-row p {
    margin: 0;
    font-size: 0.9rem;
    white-space: pre-wrap;
}

/* ---------- Admin viewer's panel figcaption (see admin/tmpl/comic/default.php's
   own inline <style> for the rest of that page — this component's admin
   viewer is deliberately self-contained rather than loading comicstrip.css) ---------- */

/* ---------- Creation flow (view=create) ---------- */

.com-comicstrip-create {
    max-width: 760px;
    margin: 0 auto;
    padding: 1.5rem 1rem 3rem;
}

.cx-create-step h1 {
    margin-bottom: 0.35rem;
}

.cx-create-step > p {
    color: var(--cx-text-muted);
    margin-bottom: 1.5rem;
}

.cx-create-field {
    margin-bottom: 1.75rem;
}

.cx-create-field-label {
    display: block;
    font-weight: 700;
    margin-bottom: 0.6rem;
}

.cx-create-field-required {
    color: var(--cx-danger);
}

.cx-create-choice-group {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.cx-create-choice {
    padding: 0.6rem 1.1rem;
    border: 2px solid var(--cx-border);
    border-radius: var(--cx-radius);
    background: var(--cx-surface);
    color: var(--cx-text);
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.cx-create-choice:hover {
    border-color: var(--cx-accent);
}

.cx-create-choice.is-active {
    border-color: var(--cx-accent);
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
}

.cx-create-custom-count {
    margin-top: 0.75rem;
    width: 100%;
    max-width: 220px;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.95rem;
}

.cx-create-field-error {
    color: var(--cx-danger);
    font-size: 0.8rem;
    margin: 0.5rem 0 0;
}

.cx-create-direction-group {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.75rem;
}

.cx-create-direction-choice {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.9rem 1rem;
    text-align: left;
}

.cx-create-direction-choice i {
    font-size: 1.2rem;
    color: var(--cx-accent);
}

.cx-create-direction-label {
    font-weight: 700;
}

.cx-create-direction-desc {
    font-weight: 400;
    font-size: 0.8rem;
    color: var(--cx-text-muted);
}

#cx-create-title {
    width: 100%;
    max-width: 420px;
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.95rem;
}

.cx-create-plan-table-wrap {
    overflow-x: auto;
    margin-bottom: 1.25rem;
}

.cx-create-plan-table {
    width: 100%;
    border-collapse: collapse;
}

.cx-create-plan-table th {
    text-align: left;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--cx-text-muted);
    padding: 0 0.6rem 0.5rem;
}

.cx-create-plan-table td {
    vertical-align: top;
    padding: 0.4rem 0.6rem;
    border-top: 1px solid var(--cx-border);
}

.cx-create-plan-col-panel {
    width: 5.5rem;
}

.cx-create-plan-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: var(--cx-accent-soft);
    color: var(--cx-accent-dark);
    font-weight: 700;
    font-size: 0.85rem;
    margin-right: 0.4rem;
}

.cx-create-plan-shape {
    font-size: 0.72rem;
    color: var(--cx-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.cx-create-plan-synopsis {
    width: 100%;
    min-height: 2.4rem;
    padding: 0.4rem 0.55rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.88rem;
    font-family: inherit;
    resize: vertical;
}

.cx-create-plan-dialogue {
    width: 100%;
    min-height: 2.4rem;
    padding: 0.4rem 0.55rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    font-size: 0.88rem;
    font-family: inherit;
    resize: vertical;
}

.cx-create-plan-actions {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
}

@media (max-width: 640px) {
    .cx-create-plan-table thead {
        display: none;
    }

    .cx-create-plan-table tr {
        display: block;
        margin-bottom: 1rem;
    }

    .cx-create-plan-table td {
        display: block;
        width: 100%;
        border-top: none;
        padding: 0.3rem 0;
    }
}

/* ---------- Stage nav ---------- */
/* "01 Planning · 02 Develop Your Story · 03 Create Your Comic" — shown on
   every post-creation view (see StageNavHelper). Any not-yet-built stage
   renders as a <span>, not an <a>, styled identically minus the pointer
   cursor/hover so it reads as "not yet" rather than "broken link". */

.cx-stage-nav {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.cx-stage-nav-item {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--cx-border);
    border-radius: 999px;
    background: var(--cx-surface);
    color: var(--cx-text-muted);
    font-size: 0.82rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

a.cx-stage-nav-item:hover {
    border-color: var(--cx-accent);
    color: var(--cx-accent-dark);
    background: var(--cx-accent-soft);
}

.cx-stage-nav-item.is-active {
    border-color: var(--cx-accent);
    background: var(--cx-accent);
    color: #fff;
}

.cx-stage-nav-item.is-disabled {
    cursor: default;
    opacity: 0.55;
}

.cx-stage-nav-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 50%;
    background: rgba(17, 24, 39, 0.08);
    font-size: 0.68rem;
}

.cx-stage-nav-item.is-active .cx-stage-nav-number {
    background: rgba(255, 255, 255, 0.25);
}

@media (max-width: 640px) {
    .cx-stage-nav-label {
        display: none;
    }
}

/* ---------- Planning page ---------- */

.com-comicstrip-planning {
    padding: 1.5rem 1.5rem 3rem;
}

.cx-planning-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.25rem;
}

.cx-planning-title-group h1 {
    margin: 0;
}

.cx-planning-eyebrow {
    margin: 0 0 0.15rem;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--cx-accent);
}

.cx-planning-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.cx-planning-intro {
    margin: 0 0 1.5rem;
    color: var(--cx-text-muted);
    font-size: 0.9rem;
    max-width: 60ch;
}

.cx-planning-section {
    margin-bottom: 2rem;
}

.cx-planning-panels-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.cx-planning-panels-header h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
}

.cx-planning-empty {
    margin: 1rem 0;
    color: var(--cx-text-muted);
    font-size: 0.85rem;
}

/* Planning reuses the Editor's old Arrange-Panels sidebar/grid classes
   verbatim (.cx-arrange-*) — same size picker, same spatial grid — just
   without the modal chrome around them, and with Duplicate/Delete added to
   the sidebar instead of story-planning textareas (those stay owned by the
   Editor's planning strip for now). */
.cx-planning-sidebar-quick-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

@media (max-width: 900px) {
    .com-comicstrip-planning,
    .com-comicstrip-develop {
        padding: 1rem 1rem 2.5rem;
    }

    .cx-arrange-layout,
    .cx-develop-body {
        flex-direction: column;
        display: flex;
    }

    .cx-arrange-sidebar,
    .cx-develop-body .cx-filmstrip {
        width: 100%;
        flex-basis: auto;
    }

    .cx-develop-body .cx-filmstrip-list {
        flex-direction: row;
        overflow-x: auto;
    }

    .cx-develop-body .cx-filmstrip-item {
        min-width: 90px;
        flex-shrink: 0;
    }
}

/* ---------- Develop page ---------- */
/* Reuses .cx-planning-header/-title-group/-eyebrow/-actions/-intro and
   .com-comicstrip-planning's padding verbatim (see StageNavHelper's header
   markup in tmpl/develop/default.php) — only the body below the intro is
   new. */

.com-comicstrip-develop {
    padding: 1.5rem 1.5rem 3rem;
}

.cx-develop-body {
    display: grid;
    grid-template-columns: 160px 1fr;
    gap: 1.25rem;
    align-items: start;
}

.cx-develop-body .cx-filmstrip {
    max-height: 70vh;
}

.cx-develop-form-area {
    max-width: 640px;
}

.cx-develop-form-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.cx-develop-form-header h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
}

.cx-develop-field {
    margin-bottom: 1.1rem;
}

.cx-develop-field label {
    display: block;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--cx-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.4rem;
}

.cx-develop-field input[type="text"],
.cx-develop-field textarea {
    width: 100%;
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--cx-border-strong);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    font-family: inherit;
    font-size: 0.9rem;
    color: var(--cx-text);
    resize: vertical;
}

.cx-develop-field input[type="text"]:focus,
.cx-develop-field textarea:focus {
    outline: none;
    border-color: var(--cx-accent);
    box-shadow: 0 0 0 3px var(--cx-accent-soft);
}
