stream_weaver 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.beads/.gitignore +77 -0
- data/.beads/README.md +81 -0
- data/.beads/config.yaml +54 -0
- data/.beads/metadata.json +7 -0
- data/.rspec +3 -0
- data/AGENTS.md +91 -0
- data/CHANGELOG.md +294 -0
- data/CLAUDE.md +67 -0
- data/LICENSE.txt +21 -0
- data/README.md +565 -0
- data/Rakefile +13 -0
- data/assets/streamweaver-hero.jpg +0 -0
- data/bench/baselines/ledger.rb +58 -0
- data/bench/baselines/warroom.rb +51 -0
- data/bench/fixtures/ledger.rb +76 -0
- data/bench/fixtures/warroom.rb +60 -0
- data/bench/results/6daf8b8.md +102 -0
- data/bench/results/c8d749b.md +102 -0
- data/bench/results/eb06ab9.md +102 -0
- data/bench/run.rb +151 -0
- data/bench/support.rb +84 -0
- data/copy_this/dto_run_sample_run_7631.html +220 -0
- data/docs/SERVICE_MODE.md +302 -0
- data/docs/architecture/how_streamweaver_works.md +772 -0
- data/docs/blog/2026-01-16-streamweaver-introduction.md +515 -0
- data/docs/bug-2026-08-31-canvas-bridge-socket-collision.md +83 -0
- data/docs/canvas-ipc-session-summary.md +127 -0
- data/docs/canvas-panel-workflow.md +172 -0
- data/docs/canvas-read.md +147 -0
- data/docs/canvas-roadmap.md +275 -0
- data/docs/case-studies/2026-09-03-didx-canvas-read-shelf.md +147 -0
- data/docs/claude-code-companion-skill-spec.md +567 -0
- data/docs/components_reference.md +1223 -0
- data/docs/crud-patterns.md +265 -0
- data/docs/endpoints.md +122 -0
- data/docs/for_llms.md +1 -0
- data/docs/form-patterns.md +165 -0
- data/docs/frontend-only.md +169 -0
- data/docs/html-artifact-audit.md +238 -0
- data/docs/ideas/2025-01-01-as-a-service.md +203 -0
- data/docs/ideas/2026-01-16-charm-tui-exploration.md +542 -0
- data/docs/ideas/2026-08-27-static-doc-shelf-export.md +153 -0
- data/docs/ideas/dhh-review-future-refactors.md +142 -0
- data/docs/opal-jamstack.md +47 -0
- data/docs/opal-npm-direction.md +184 -0
- data/docs/opal-spike-findings.md +194 -0
- data/docs/plans/2025-12-16-form-blocks-design.md +124 -0
- data/docs/plans/2025-12-30-examples-browser-design.md +152 -0
- data/docs/plans/2026-01-02-charts-design.md +288 -0
- data/docs/plans/2026-01-10-canvas-ipc-design.md +247 -0
- data/docs/plans/2026-01-19-cabinet-control-components.md +176 -0
- data/docs/plans/2026-01-26-gem-release-and-panel.md +140 -0
- data/docs/plans/canvas-claude-project-design.md +238 -0
- data/docs/plans/canvas-doc-location-and-discovery.md +32 -0
- data/docs/plans/org-doc-preview-surfaces.md +293 -0
- data/docs/plans/shadcn-polish-plan.md +417 -0
- data/docs/porting-artifacts.md +252 -0
- data/docs/reference/agent-skills-comparison.md +369 -0
- data/docs/reference/travel-state-prd.artifact.html +780 -0
- data/docs/research/2026-08-17-hotwire-alike-landscape.md +268 -0
- data/docs/research/2026-08-17-hotwire-concept-map.md +404 -0
- data/docs/research/2026-08-22-lazy-fragments-trigger-decision.md +246 -0
- data/docs/research/2026-08-22-learnhotwire-syllabus-coverage.md +239 -0
- data/docs/research/frontend-only-matrix.md +278 -0
- data/docs/research/streamweaver-way-spike-findings.md +529 -0
- data/docs/resource-dsl.md +342 -0
- data/docs/routing.md +302 -0
- data/docs/ruby-ui-comparison.md +319 -0
- data/docs/shared-dsl-fragments.md +356 -0
- data/docs/streamweaver-for-ai-agents.md +401 -0
- data/docs/streamweaver-frontend-vision.md +123 -0
- data/docs/streamweaver_canvas/glimmer_initial_final_layer.rb +77 -0
- data/docs/streamweaver_canvas/save_example.rb +9 -0
- data/docs/templates.md +213 -0
- data/docs/testing.md +155 -0
- data/docs/theming-hooks.md +281 -0
- data/docs/tutorials/the-streamweaver-way.md +882 -0
- data/docs/university/capability-inventory.md +137 -0
- data/docs/university/dependency-survey.md +114 -0
- data/docs/university/design-spec.md +217 -0
- data/docs/university/mockups/code-block-doc-theme-dark.png +0 -0
- data/docs/university/mockups/code-block-doc-theme-light.png +0 -0
- data/docs/university/mockups/course-list-dark.png +0 -0
- data/docs/university/mockups/course-list-light.png +0 -0
- data/docs/university/mockups/course_canvas_mockup.rb +741 -0
- data/docs/university/mockups/step-screen-dark.png +0 -0
- data/docs/university/mockups/step-screen-light.png +0 -0
- data/docs/university/roadmap.md +72 -0
- data/docs/university/send-to-coworker.md +77 -0
- data/docs/university/worker-session-mining.md +109 -0
- data/docs/visual/browser-rendering-spike.md +173 -0
- data/docs/visual/builder-visual-plan-analysis.md +378 -0
- data/docs/visual/sw-plan-format-exploration.md +511 -0
- data/docs/visual/sw-plan-rendering-deep-dive.md +453 -0
- data/docs/visual-skills/PROGRESS.md +60 -0
- data/docs/visual-skills/SESSION-CONTEXT.md +207 -0
- data/docs/visual-skills/analysis/components.md +317 -0
- data/docs/visual-skills/analysis/overlap.md +360 -0
- data/docs/visual-skills/analysis/pi-design-deck.md +1102 -0
- data/docs/visual-skills/analysis/streamweaver-inventory.md +93 -0
- data/docs/visual-skills/analysis/unified-specs.feature +1003 -0
- data/docs/visual-skills/analysis/visual-explainer.md +964 -0
- data/docs/visual-skills/blog/blog-series-outline.md +39 -0
- data/docs/visual-skills/blog/token-efficiency.md +73 -0
- data/docs/visual-skills/design/architecture.md +1725 -0
- data/docs/visual-skills/design/codex-review.md +340 -0
- data/docs/visual-skills/design/dhh-review.md +209 -0
- data/docs/visual-skills/design/evolution.md +100 -0
- data/docs/visual-skills/design/gemini-review.md +55 -0
- data/docs/visual-skills/design/review-synthesis.md +149 -0
- data/docs/visual-skills/implementation/STATE.md +22 -0
- data/docs/visual-skills/implementation/plan.md +347 -0
- data/docs/visual-skills/implementation/spike-findings.md +237 -0
- data/docs/visual-skills/implementation/tasks.md +702 -0
- data/docs/visual-skills/lessons-learned/process.md +145 -0
- data/examples/README.md +96 -0
- data/examples/advanced/all_components.rb +177 -0
- data/examples/advanced/examples_browser.rb +394 -0
- data/examples/advanced/teachables_browser.rb +261 -0
- data/examples/advanced/theme_tweaker.rb +366 -0
- data/examples/advanced/tutorial.rb +1528 -0
- data/examples/agentic/agentic_form.rb +30 -0
- data/examples/agentic/agentic_form_autoclose.rb +30 -0
- data/examples/agentic/cultivation_tracker.rb +69 -0
- data/examples/basic/hello_world.rb +22 -0
- data/examples/basic/opal_tabs_table.rb +44 -0
- data/examples/basic/todo_list.rb +39 -0
- data/examples/button_loading_test.rb +36 -0
- data/examples/canvas/mermaid_canvas_demo.sh +151 -0
- data/examples/charts/bar_chart_demo.rb +57 -0
- data/examples/charts/line_chart_demo.rb +73 -0
- data/examples/charts/pie_area_demo.rb +87 -0
- data/examples/charts/stacked_bar_chart_demo.rb +69 -0
- data/examples/claude_code/README.md +85 -0
- data/examples/claude_code/codebreaker/.claude/commands/infiltrate.md +632 -0
- data/examples/claude_code/codebreaker/.claude/settings.json +11 -0
- data/examples/claude_code/codebreaker/.claude/settings.local.json +17 -0
- data/examples/claude_code/codebreaker/.claude/skills/infiltrate.md +0 -0
- data/examples/claude_code/codebreaker/README.md +213 -0
- data/examples/claude_code/tutorial/.claude/commands/learn.md +453 -0
- data/examples/claude_code/tutorial/README.md +102 -0
- data/examples/claude_code/verification_flow/.claude/commands/verify.md +171 -0
- data/examples/claude_code/verification_flow/README.md +126 -0
- data/examples/components/annotated_code_demo.rb +54 -0
- data/examples/components/callout_demo.rb +33 -0
- data/examples/components/checkbox_group_demo.rb +46 -0
- data/examples/components/design_review.css +468 -0
- data/examples/components/design_review_demo.rb +44 -0
- data/examples/components/design_review_dsl.rb +300 -0
- data/examples/components/diff_block_demo.rb +112 -0
- data/examples/components/events_demo.rb +184 -0
- data/examples/components/form_demo.rb +62 -0
- data/examples/components/lesson_demo.rb +53 -0
- data/examples/components/markdown_demo.rb +110 -0
- data/examples/components/mermaid_demo.rb +166 -0
- data/examples/components/pareto_set.rb +64 -0
- data/examples/components/prd_demo.rb +21 -0
- data/examples/components/prd_dsl.rb +385 -0
- data/examples/components/quiz_demo.rb +47 -0
- data/examples/components/run_viewer_demo.rb +100 -0
- data/examples/components/score_and_collapsible_demo.rb +82 -0
- data/examples/components/select_stale_value_smoke_test.rb +39 -0
- data/examples/components/table_demo.rb +247 -0
- data/examples/components/timer_with_state_demo.rb +58 -0
- data/examples/components/todo_due_dates.rb +63 -0
- data/examples/components/uat_gaps_demo.rb +112 -0
- data/examples/dashboard/feed_simulator.rb +60 -0
- data/examples/dashboard/live_dashboard.rb +80 -0
- data/examples/dashboard_components.rb +108 -0
- data/examples/deferred_fragments_demo.rb +62 -0
- data/examples/generate_more_spike/README.md +63 -0
- data/examples/generate_more_spike/app.rb +441 -0
- data/examples/git_health.sh +401 -0
- data/examples/layout/layout_components_demo.rb +174 -0
- data/examples/layout/modal_demo.rb +215 -0
- data/examples/layout/navigation_demo.rb +227 -0
- data/examples/layout/route_tabs_demo.rb +52 -0
- data/examples/layout/routing_demo.rb +52 -0
- data/examples/layout/scroll_box_demo.rb +154 -0
- data/examples/lazy_fragments_demo.rb +151 -0
- data/examples/my_todos/README.md +29 -0
- data/examples/my_todos/my_todos.rb +312 -0
- data/examples/my_todos/store.rb +86 -0
- data/examples/opal/reactive_demo.rb +62 -0
- data/examples/opal/scenarios/s1_counter.rb +15 -0
- data/examples/opal/scenarios/s2_search_filter.rb +17 -0
- data/examples/opal/scenarios/s3_sibling_tabs.rb +25 -0
- data/examples/opal/scenarios/s4_shopping_cart.rb +34 -0
- data/examples/opal/scenarios/s5_watch.rb +39 -0
- data/examples/opal/scenarios/s6_on_start.rb +20 -0
- data/examples/opal/scenarios/s7_wizard.rb +33 -0
- data/examples/opal/scenarios/s8_loan_calculator.rb +21 -0
- data/examples/opal/scenarios/s9_dashboard.rb +27 -0
- data/examples/operations_dashboard_demo.rb +167 -0
- data/examples/panel_demo.sh +447 -0
- data/examples/parity/assets/tyrion_slice.css +86 -0
- data/examples/parity/rivet_people_slice.rb +182 -0
- data/examples/parity/tyrion_components.css +777 -0
- data/examples/parity/tyrion_warroom_components.rb +414 -0
- data/examples/parity/tyrion_warroom_slice.rb +549 -0
- data/examples/puma_dev/README.md +124 -0
- data/examples/puma_dev/config.ru +34 -0
- data/examples/puma_dev/standalone_app.rb +34 -0
- data/examples/scaffolding/blog.rb +49 -0
- data/examples/scaffolding/utf_lite.rb +105 -0
- data/examples/styling/feedback_demo.rb +229 -0
- data/examples/styling/style_showcase.rb +172 -0
- data/examples/styling/theme_demo.rb +508 -0
- data/examples/timer_health_checker.rb +111 -0
- data/examples/timer_showcase.rb +131 -0
- data/examples/tutorials/render_markdown.rb +112 -0
- data/examples/tutorials/streamweaver_way_tutorial.rb +44 -0
- data/examples/tutorials/tutorial_content.rb +1082 -0
- data/examples/visual_skills/design_deck_demo.rb +314 -0
- data/examples/visual_skills/explainer_demo.rb +499 -0
- data/exe/streamweaver +9 -0
- data/gsd/ROADMAP-1.0.md +167 -0
- data/gsd/STATE.md +13 -0
- data/gsd/research/market-positioning-research.md +139 -0
- data/gsd/research/production-patterns-research.md +270 -0
- data/gsd/research/repo-audit-1.0.md +274 -0
- data/lib/stream_weaver/action_token.rb +58 -0
- data/lib/stream_weaver/adapter/alpinejs.rb +8249 -0
- data/lib/stream_weaver/adapter/base.rb +591 -0
- data/lib/stream_weaver/adapter/opal.rb +334 -0
- data/lib/stream_weaver/adapter/static.rb +1118 -0
- data/lib/stream_weaver/admin.rb +176 -0
- data/lib/stream_weaver/app.rb +1821 -0
- data/lib/stream_weaver/assets/js/sw-copy.js +50 -0
- data/lib/stream_weaver/assets/js/sw-heredoc-rewrite.js +68 -0
- data/lib/stream_weaver/assets/js/sw-keyboard.js +165 -0
- data/lib/stream_weaver/assets/js/sw-mermaid-zoom.js +621 -0
- data/lib/stream_weaver/assets/js/sw-route-tabs.js +53 -0
- data/lib/stream_weaver/assets/js/sw-sidebar-toc.js +116 -0
- data/lib/stream_weaver/assets/js/sw-slide-nav.js +103 -0
- data/lib/stream_weaver/canvas/bridge.rb +239 -0
- data/lib/stream_weaver/canvas/bridge_server.rb +646 -0
- data/lib/stream_weaver/canvas/client.rb +298 -0
- data/lib/stream_weaver/canvas/doc_roots.rb +233 -0
- data/lib/stream_weaver/canvas/doc_store.rb +252 -0
- data/lib/stream_weaver/canvas/gist_publisher.rb +264 -0
- data/lib/stream_weaver/canvas/gist_save_handler.rb +89 -0
- data/lib/stream_weaver/canvas/gist_store.rb +135 -0
- data/lib/stream_weaver/canvas/helpers.rb +109 -0
- data/lib/stream_weaver/canvas/history.rb +90 -0
- data/lib/stream_weaver/canvas/protocol.rb +86 -0
- data/lib/stream_weaver/canvas/reader.rb +853 -0
- data/lib/stream_weaver/canvas/save_doc_widget.rb +457 -0
- data/lib/stream_weaver/canvas/scroll_top_hint.rb +21 -0
- data/lib/stream_weaver/canvas/session.rb +132 -0
- data/lib/stream_weaver/cli.rb +3235 -0
- data/lib/stream_weaver/component_assets.rb +70 -0
- data/lib/stream_weaver/component_registry.rb +67 -0
- data/lib/stream_weaver/component_renderer.rb +48 -0
- data/lib/stream_weaver/components/annotated_code.rb +53 -0
- data/lib/stream_weaver/components/api_endpoint.rb +42 -0
- data/lib/stream_weaver/components/callout.rb +59 -0
- data/lib/stream_weaver/components/chart.rb +84 -0
- data/lib/stream_weaver/components/code_block.rb +77 -0
- data/lib/stream_weaver/components/comparison.rb +39 -0
- data/lib/stream_weaver/components/decision.rb +38 -0
- data/lib/stream_weaver/components/deck/close_overlay.rb +84 -0
- data/lib/stream_weaver/components/deck/confirmation_bar.rb +60 -0
- data/lib/stream_weaver/components/deck/deck_option.rb +61 -0
- data/lib/stream_weaver/components/deck/deck_slide.rb +77 -0
- data/lib/stream_weaver/components/deck/deck_state.rb +469 -0
- data/lib/stream_weaver/components/deck/deck_summary.rb +73 -0
- data/lib/stream_weaver/components/deck/design_deck.rb +60 -0
- data/lib/stream_weaver/components/deck/generate_more_controls.rb +84 -0
- data/lib/stream_weaver/components/deck/model_selector.rb +84 -0
- data/lib/stream_weaver/components/deck/skeleton_placeholder.rb +36 -0
- data/lib/stream_weaver/components/diff_block.rb +125 -0
- data/lib/stream_weaver/components/doc_header.rb +57 -0
- data/lib/stream_weaver/components/image_block.rb +70 -0
- data/lib/stream_weaver/components/implementation_map.rb +33 -0
- data/lib/stream_weaver/components/keyboard_shortcuts.rb +94 -0
- data/lib/stream_weaver/components/kpi_dashboard.rb +78 -0
- data/lib/stream_weaver/components/mermaid.rb +79 -0
- data/lib/stream_weaver/components/pipeline.rb +63 -0
- data/lib/stream_weaver/components/sidebar_toc.rb +46 -0
- data/lib/stream_weaver/components/slide_container.rb +141 -0
- data/lib/stream_weaver/components/timeline_event.rb +58 -0
- data/lib/stream_weaver/components/wireframe.rb +29 -0
- data/lib/stream_weaver/components/wireframe_block.rb +34 -0
- data/lib/stream_weaver/components.rb +2711 -0
- data/lib/stream_weaver/css.rb +230 -0
- data/lib/stream_weaver/dev_fallback_overlay.rb +61 -0
- data/lib/stream_weaver/display_dsl.rb +1004 -0
- data/lib/stream_weaver/export/html_exporter.rb +478 -0
- data/lib/stream_weaver/feed.rb +34 -0
- data/lib/stream_weaver/feed_builder.rb +31 -0
- data/lib/stream_weaver/fonts.rb +33 -0
- data/lib/stream_weaver/interaction_runner.rb +487 -0
- data/lib/stream_weaver/iterm.rb +460 -0
- data/lib/stream_weaver/layout_registry.rb +92 -0
- data/lib/stream_weaver/opal/bridge.rb +52 -0
- data/lib/stream_weaver/opal/builder.rb +193 -0
- data/lib/stream_weaver/opal/env.rb +29 -0
- data/lib/stream_weaver/opal/reactive_state.rb +75 -0
- data/lib/stream_weaver/opal/regexp_anchor_patch.rb +62 -0
- data/lib/stream_weaver/opal/renderer.rb +77 -0
- data/lib/stream_weaver/opal/runtime.rb +250 -0
- data/lib/stream_weaver/opal/shell.rb +145 -0
- data/lib/stream_weaver/opal/string_bridge.rb +50 -0
- data/lib/stream_weaver/opal/stubs/diff.min.js +1 -0
- data/lib/stream_weaver/opal/stubs/digest.rb +15 -0
- data/lib/stream_weaver/opal/stubs/marked.umd.js +79 -0
- data/lib/stream_weaver/opal/stubs/md5.rb +3 -0
- data/lib/stream_weaver/opal/stubs/morphdom.min.js +775 -0
- data/lib/stream_weaver/opal/stubs/prism-tomorrow.min.css +1 -0
- data/lib/stream_weaver/opal/stubs/prism.min.js +1967 -0
- data/lib/stream_weaver/opal_entry.rb +136 -0
- data/lib/stream_weaver/org/inline.rb +95 -0
- data/lib/stream_weaver/org/reader.rb +561 -0
- data/lib/stream_weaver/org/recording_context.rb +86 -0
- data/lib/stream_weaver/org/source_splitter.rb +63 -0
- data/lib/stream_weaver/org/writer.rb +300 -0
- data/lib/stream_weaver/page_shell.rb +530 -0
- data/lib/stream_weaver/portfile.rb +79 -0
- data/lib/stream_weaver/pushable.rb +42 -0
- data/lib/stream_weaver/resource/default_views.rb +111 -0
- data/lib/stream_weaver/resource/field_input.rb +25 -0
- data/lib/stream_weaver/resource/state_keys.rb +16 -0
- data/lib/stream_weaver/resource/store.rb +18 -0
- data/lib/stream_weaver/resource.rb +104 -0
- data/lib/stream_weaver/server.rb +1471 -0
- data/lib/stream_weaver/service.rb +1240 -0
- data/lib/stream_weaver/service_client.rb +104 -0
- data/lib/stream_weaver/session_store.rb +186 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/SKILL.md +66 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/examples/canvas-safe-showcase.rb +98 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/actions-and-buttons.md +47 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/charts-and-diagrams.md +38 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/deck.md +32 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/inputs-and-forms.md +65 -0
- data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/tabs-and-navigation.md +53 -0
- data/lib/stream_weaver/skills/streamweaver-doc-builder/SKILL.md +298 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/SKILL.md +130 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.css +468 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.rb +39 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example_dsl.rb +300 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example.rb +24 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example_dsl.rb +385 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/checkpoints-and-forms.md +28 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/cleanup-and-panel.md +44 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/example-gallery.md +8 -0
- data/lib/stream_weaver/skills/streamweaver-visual-companion/references/persistence.md +58 -0
- data/lib/stream_weaver/skills/streamweaver-way/SKILL.md +396 -0
- data/lib/stream_weaver/skills/visual-plan/SKILL.md +201 -0
- data/lib/stream_weaver/skills/visual-recap/SKILL.md +244 -0
- data/lib/stream_weaver/streamer.rb +63 -0
- data/lib/stream_weaver/templates/choices.rb +149 -0
- data/lib/stream_weaver/templates/code.rb +157 -0
- data/lib/stream_weaver/templates/confirm.rb +110 -0
- data/lib/stream_weaver/templates/diff.rb +195 -0
- data/lib/stream_weaver/templates/info.rb +142 -0
- data/lib/stream_weaver/templates/table.rb +169 -0
- data/lib/stream_weaver/templates/wizard.rb +271 -0
- data/lib/stream_weaver/theme/auto_mode.rb +116 -0
- data/lib/stream_weaver/theme/presets.rb +555 -0
- data/lib/stream_weaver/theme.rb +639 -0
- data/lib/stream_weaver/university/canvas.rb +924 -0
- data/lib/stream_weaver/university/course.rb +699 -0
- data/lib/stream_weaver/university/demos/counter.rb +33 -0
- data/lib/stream_weaver/university/demos/dashboard.rb +147 -0
- data/lib/stream_weaver/university/demos/decision_form.rb +143 -0
- data/lib/stream_weaver/university/demos.rb +50 -0
- data/lib/stream_weaver/university/listener.rb +467 -0
- data/lib/stream_weaver/university/progress.rb +201 -0
- data/lib/stream_weaver/university/runner.rb +138 -0
- data/lib/stream_weaver/university/scripts/growing_doc.rb +609 -0
- data/lib/stream_weaver/university/scripts/growing_doc_state.rb +91 -0
- data/lib/stream_weaver/utils.rb +31 -0
- data/lib/stream_weaver/version.rb +5 -0
- data/lib/stream_weaver/views/canvas/reader_layout.erb +800 -0
- data/lib/stream_weaver/views.rb +3940 -0
- data/lib/stream_weaver.rb +122 -0
- data/llms.txt +1269 -0
- data/sig/stream_weaver.rbs +4 -0
- metadata +598 -0
|
@@ -0,0 +1,3940 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'phlex'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module StreamWeaver
|
|
7
|
+
module Views
|
|
8
|
+
# Full page view for initial load (includes <html>, <head>, <body>)
|
|
9
|
+
class AppView < Phlex::HTML
|
|
10
|
+
attr_reader :adapter, :app
|
|
11
|
+
attr_reader :current_fragment_id
|
|
12
|
+
|
|
13
|
+
# @param app [StreamWeaver::App] The app instance
|
|
14
|
+
# @param state [Hash] The current state
|
|
15
|
+
# @param adapter [StreamWeaver::Adapter::Base] The adapter for rendering
|
|
16
|
+
# @param is_agentic [Boolean] Whether running in agentic mode
|
|
17
|
+
# @param session_theme [Symbol, nil] Theme override from session
|
|
18
|
+
def initialize(app, state, adapter, is_agentic = false, session_theme: nil)
|
|
19
|
+
@app = app
|
|
20
|
+
@state = state
|
|
21
|
+
@adapter = adapter
|
|
22
|
+
@is_agentic = is_agentic
|
|
23
|
+
@session_theme = session_theme
|
|
24
|
+
@scripts = app.scripts
|
|
25
|
+
@stylesheets = app.stylesheets
|
|
26
|
+
@inline_stylesheets = app.inline_stylesheets
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def view_template
|
|
30
|
+
# Render the body content first so that any component CSS injected
|
|
31
|
+
# along the way (adapter#inject_component_css) lands in
|
|
32
|
+
# @component_css_registry before <head> is built below -- <head>
|
|
33
|
+
# must come first in document order, but which components (and thus
|
|
34
|
+
# which CSS) will render is only known by actually rendering them
|
|
35
|
+
# (stream_weaver-1lo). Captured rather than appended directly so it
|
|
36
|
+
# can be replayed into <body> after <head> is emitted.
|
|
37
|
+
body_html = capture { render_page_body }
|
|
38
|
+
|
|
39
|
+
doctype
|
|
40
|
+
html do
|
|
41
|
+
head do
|
|
42
|
+
# Pins cascade layer order before any framework CSS is emitted
|
|
43
|
+
# (stream_weaver-oeo). All framework CSS lives in this one named
|
|
44
|
+
# layer; any unlayered user stylesheet always wins the cascade
|
|
45
|
+
# regardless of specificity or document order, so the
|
|
46
|
+
# stream_weaver-1lo hoist-to-<head> trick below is now belt-and-
|
|
47
|
+
# suspenders rather than the only thing making user CSS win.
|
|
48
|
+
style { raw(safe("@layer #{StreamWeaver::CSS::LAYER_NAME};")) }
|
|
49
|
+
|
|
50
|
+
title { @app.title }
|
|
51
|
+
meta(charset: "utf-8")
|
|
52
|
+
meta(name: "viewport", content: "width=device-width, initial-scale=1")
|
|
53
|
+
# Favicon (emoji or URL)
|
|
54
|
+
if (fav = @app.favicon_href)
|
|
55
|
+
link(rel: "icon", href: fav)
|
|
56
|
+
end
|
|
57
|
+
# Inject adapter-specific CDN scripts using Phlex methods
|
|
58
|
+
@adapter.render_cdn_scripts(self)
|
|
59
|
+
|
|
60
|
+
# Framework-injected component CSS (collected during the body
|
|
61
|
+
# render above), before user stylesheets: below -- so that at
|
|
62
|
+
# equal specificity, user stylesheets win on document order
|
|
63
|
+
# instead of always losing to the framework's own rule
|
|
64
|
+
# (stream_weaver-1lo). Also wrapped in the shared layer
|
|
65
|
+
# (stream_weaver-oeo) so this ordering is now a nicety, not a
|
|
66
|
+
# requirement -- an unlayered user rule wins even if it lands
|
|
67
|
+
# earlier in the document.
|
|
68
|
+
unless component_css_registry.empty?
|
|
69
|
+
style { raw(safe(StreamWeaver::CSS.layer_wrap(component_css_registry.values.join("\n")))) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Add custom stylesheets
|
|
73
|
+
@stylesheets.each do |href|
|
|
74
|
+
link(rel: "stylesheet", href: href)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Inline stylesheets declared via `use_stylesheet` in the DSL
|
|
78
|
+
# body (stream_weaver-9uk) -- same document-order/cascade
|
|
79
|
+
# position as the linked stylesheets above.
|
|
80
|
+
@inline_stylesheets.each do |css|
|
|
81
|
+
style { raw(safe(css)) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Add custom scripts
|
|
85
|
+
@scripts.each do |src|
|
|
86
|
+
script(src: src)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Component-scoped CSS/JS (declared via css/css_path/js_path class macros)
|
|
90
|
+
render_component_assets
|
|
91
|
+
|
|
92
|
+
# Chart.js CDN - only load when charts are present
|
|
93
|
+
if @app.has_charts?
|
|
94
|
+
script(src: "https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# SSE client for streaming push updates (when app has stream block or timers)
|
|
98
|
+
if (@app.stream_block || @app.has_timers?) && @adapter.respond_to?(:render_sse_client)
|
|
99
|
+
@adapter.render_sse_client(self)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# URL routing: popstate handler for browser back/forward
|
|
103
|
+
if @app.routes && @adapter.respond_to?(:render_routing_scripts)
|
|
104
|
+
@adapter.render_routing_scripts(self)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Google Fonts: Source Sans 3 + Crimson Pro (for document theme) + any app-declared fonts
|
|
108
|
+
link(rel: "preconnect", href: "https://fonts.googleapis.com")
|
|
109
|
+
link(rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "anonymous")
|
|
110
|
+
link(rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600&family=Source+Sans+3:wght@400;500;600;700&display=swap")
|
|
111
|
+
if @app.fonts.any?
|
|
112
|
+
custom_href = Fonts.google_fonts_href(@app.fonts)
|
|
113
|
+
link(rel: "stylesheet", href: custom_href) if custom_href
|
|
114
|
+
end
|
|
115
|
+
style do
|
|
116
|
+
raw(safe(StreamWeaver::CSS.layer_wrap(self.class.master_theme_css)))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Visual Skills CSS foundation (--sw-* semantic tokens)
|
|
120
|
+
style do
|
|
121
|
+
raw(safe(StreamWeaver::CSS.layer_wrap(StreamWeaver::Theme.visual_skills_css)))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Dev-loud-failure overlay styling (dev-loud-failure-overlay).
|
|
125
|
+
# Always emitted -- inert unless RACK_ENV=development actually
|
|
126
|
+
# renders `.sw-dev-fallback` markup into a fallback response.
|
|
127
|
+
style do
|
|
128
|
+
raw(safe(StreamWeaver::CSS.layer_wrap(StreamWeaver::CSS.dev_fallback_css)))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Dark mode: check localStorage / system preference, apply .dark on <html>
|
|
132
|
+
# Also provides swToggleTheme() / swGetTheme() for auto-mode support
|
|
133
|
+
script do
|
|
134
|
+
theme_toggle = @app.components.find { |c| c.is_a?(Components::ThemeToggle) }
|
|
135
|
+
raw(safe(StreamWeaver::Theme::AutoMode.inline_script(default_mode: theme_toggle&.mode || :auto)))
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
body(class: body_classes) do
|
|
139
|
+
raw(safe(body_html))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Everything that lives inside <body> -- extracted so view_template
|
|
145
|
+
# can render it once via `capture` (see the comment there) and replay
|
|
146
|
+
# the resulting string after <head> is built.
|
|
147
|
+
def render_page_body
|
|
148
|
+
# Custom theme CSS (for registered themes)
|
|
149
|
+
render_custom_theme_css
|
|
150
|
+
|
|
151
|
+
# Theme overrides as inline CSS
|
|
152
|
+
render_theme_overrides if @app.theme_overrides.any?
|
|
153
|
+
|
|
154
|
+
layout_entry = @app.layout_entry
|
|
155
|
+
if layout_entry&.dig(:exclusive) && layout_entry[:render_block]
|
|
156
|
+
instance_exec(&layout_entry[:render_block])
|
|
157
|
+
else
|
|
158
|
+
# Skip the chrome's own h1 when the app already renders its
|
|
159
|
+
# leading content as a header1 -- otherwise the page doubles up
|
|
160
|
+
# (FAC-9u2).
|
|
161
|
+
first_content = @app.components.first
|
|
162
|
+
own_leading_h1 = first_content.is_a?(Components::Header) && first_content.level == 1
|
|
163
|
+
h1 { @app.title } unless own_leading_h1
|
|
164
|
+
# Merge adapter-specific container attributes with container id
|
|
165
|
+
div(id: "app-container", "data-sw-state-version" => @app.render_state.state_version,
|
|
166
|
+
**@adapter.container_attributes(@state)) do
|
|
167
|
+
render_components
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Collects component CSS registered via adapter#inject_component_css
|
|
173
|
+
# during the body render, keyed so re-registering the same key is a
|
|
174
|
+
# no-op (cross-instance dedup within one page render).
|
|
175
|
+
def component_css_registry
|
|
176
|
+
@component_css_registry ||= {}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def register_component_css(key, css)
|
|
180
|
+
component_css_registry[key] = css
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def with_fragment(id)
|
|
184
|
+
previous = @current_fragment_id
|
|
185
|
+
@current_fragment_id = id
|
|
186
|
+
yield
|
|
187
|
+
ensure
|
|
188
|
+
@current_fragment_id = previous
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.master_theme_css
|
|
192
|
+
<<~CSS
|
|
193
|
+
/* ===========================================
|
|
194
|
+
shadcn Token Layer
|
|
195
|
+
Bridge: tokens fall back to --sw-* vars,
|
|
196
|
+
so register_theme still works.
|
|
197
|
+
=========================================== */
|
|
198
|
+
:root {
|
|
199
|
+
--background: var(--sw-color-bg, oklch(1 0 0));
|
|
200
|
+
--foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
201
|
+
--card: var(--sw-color-bg-card, oklch(1 0 0));
|
|
202
|
+
--card-foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
203
|
+
--popover: var(--sw-color-bg-card, oklch(1 0 0));
|
|
204
|
+
--popover-foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
205
|
+
--primary: var(--sw-color-primary, oklch(0.205 0 0));
|
|
206
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
207
|
+
--secondary: var(--sw-color-secondary, oklch(0.97 0 0));
|
|
208
|
+
--secondary-foreground: var(--sw-color-secondary-foreground, oklch(0.205 0 0));
|
|
209
|
+
--muted: var(--sw-color-bg-elevated, oklch(0.97 0 0));
|
|
210
|
+
--muted-foreground: var(--sw-color-text-muted, oklch(0.556 0 0));
|
|
211
|
+
--accent: var(--sw-color-accent, oklch(0.97 0 0));
|
|
212
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
213
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
214
|
+
--destructive-foreground: #fff;
|
|
215
|
+
--border: var(--sw-color-border, oklch(0.922 0 0));
|
|
216
|
+
--input: var(--sw-color-border, oklch(0.922 0 0));
|
|
217
|
+
--ring: oklch(0.708 0 0);
|
|
218
|
+
--radius: var(--sw-radius-md, 0.5rem);
|
|
219
|
+
--warning: hsl(38 92% 50%);
|
|
220
|
+
--warning-foreground: #fff;
|
|
221
|
+
--success: hsl(142 71% 45%);
|
|
222
|
+
--success-foreground: #fff;
|
|
223
|
+
--info: hsl(217 91% 60%);
|
|
224
|
+
--info-foreground: #fff;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Re-bridge on body: the --sw-color-* tokens these vars fall back to
|
|
228
|
+
are themselves declared on body (body.sw-theme-*, theme_overrides:,
|
|
229
|
+
html.dark body -- see below), which is a descendant of :root/<html>.
|
|
230
|
+
A custom property's var() fallback resolves against what's in scope
|
|
231
|
+
AT THE ELEMENT declaring it, so the :root block above can never see
|
|
232
|
+
a per-theme override made on body -- it's always stuck on its own
|
|
233
|
+
fallback. Redeclaring here, on body itself, lets these tokens see
|
|
234
|
+
whatever --sw-color-* body ends up with (default theme, a custom
|
|
235
|
+
registered theme, or an app's theme_overrides:) instead of only ever
|
|
236
|
+
reflecting the light default (stream_weaver-9u2). */
|
|
237
|
+
body {
|
|
238
|
+
--background: var(--sw-color-bg, oklch(1 0 0));
|
|
239
|
+
--foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
240
|
+
--card: var(--sw-color-bg-card, oklch(1 0 0));
|
|
241
|
+
--card-foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
242
|
+
--popover: var(--sw-color-bg-card, oklch(1 0 0));
|
|
243
|
+
--popover-foreground: var(--sw-color-text, oklch(0.145 0 0));
|
|
244
|
+
--primary: var(--sw-color-primary, oklch(0.205 0 0));
|
|
245
|
+
--secondary: var(--sw-color-secondary, oklch(0.97 0 0));
|
|
246
|
+
--secondary-foreground: var(--sw-color-secondary-foreground, oklch(0.205 0 0));
|
|
247
|
+
--muted: var(--sw-color-bg-elevated, oklch(0.97 0 0));
|
|
248
|
+
--muted-foreground: var(--sw-color-text-muted, oklch(0.556 0 0));
|
|
249
|
+
--accent: var(--sw-color-accent, oklch(0.97 0 0));
|
|
250
|
+
--border: var(--sw-color-border, oklch(0.922 0 0));
|
|
251
|
+
--input: var(--sw-color-border, oklch(0.922 0 0));
|
|
252
|
+
--radius: var(--sw-radius-md, 0.5rem);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.dark {
|
|
256
|
+
--background: oklch(0.145 0 0);
|
|
257
|
+
--foreground: oklch(0.985 0 0);
|
|
258
|
+
--card: oklch(0.205 0 0);
|
|
259
|
+
--card-foreground: oklch(0.985 0 0);
|
|
260
|
+
--popover: oklch(0.205 0 0);
|
|
261
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
262
|
+
--primary: oklch(0.922 0 0);
|
|
263
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
264
|
+
--secondary: oklch(0.269 0 0);
|
|
265
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
266
|
+
--muted: oklch(0.269 0 0);
|
|
267
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
268
|
+
--accent: oklch(0.269 0 0);
|
|
269
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
270
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
271
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
272
|
+
--border: oklch(1 0 0 / 10%);
|
|
273
|
+
--input: oklch(1 0 0 / 15%);
|
|
274
|
+
--ring: oklch(0.556 0 0);
|
|
275
|
+
--warning: hsl(38 92% 60%);
|
|
276
|
+
--warning-foreground: hsl(38 92% 10%);
|
|
277
|
+
--success: hsl(142 71% 55%);
|
|
278
|
+
--success-foreground: hsl(142 71% 10%);
|
|
279
|
+
--info: hsl(217 91% 70%);
|
|
280
|
+
--info-foreground: hsl(217 91% 10%);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Dark mode: override sw-color-* tokens to match shadcn dark values.
|
|
284
|
+
html.dark body beats body.sw-theme-* specificity (0-1-2 vs 0-1-1). */
|
|
285
|
+
html.dark body {
|
|
286
|
+
--sw-color-bg: oklch(0.145 0 0);
|
|
287
|
+
--sw-color-bg-card: oklch(0.205 0 0);
|
|
288
|
+
--sw-color-bg-elevated: oklch(0.269 0 0);
|
|
289
|
+
--sw-color-text: oklch(0.985 0 0);
|
|
290
|
+
--sw-color-text-muted: oklch(0.708 0 0);
|
|
291
|
+
--sw-color-text-light: oklch(0.556 0 0);
|
|
292
|
+
--sw-color-border: oklch(1 0 0 / 10%);
|
|
293
|
+
--sw-color-border-strong: oklch(1 0 0 / 15%);
|
|
294
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
295
|
+
--sw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
296
|
+
--sw-shadow-md: 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
297
|
+
--sw-shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
|
|
298
|
+
--sw-shadow-xl: 0 12px 24px rgba(0, 0, 0, 0.6);
|
|
299
|
+
--sw-shadow-inner: inset 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* shadcn base resets */
|
|
303
|
+
body { background-color: var(--background); color: var(--foreground); }
|
|
304
|
+
|
|
305
|
+
.sw-focus-ring:focus-visible {
|
|
306
|
+
outline: none;
|
|
307
|
+
box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* ===========================================
|
|
311
|
+
StreamWeaver CSS - Multi-Theme System
|
|
312
|
+
=========================================== */
|
|
313
|
+
|
|
314
|
+
/* Base variables (shared across all themes) */
|
|
315
|
+
:root {
|
|
316
|
+
/* Transitions - Smooth, deliberate */
|
|
317
|
+
--sw-transition-fast: 120ms ease-out;
|
|
318
|
+
--sw-transition: 200ms ease-out;
|
|
319
|
+
--sw-transition-slow: 350ms ease-out;
|
|
320
|
+
|
|
321
|
+
/* Tooltip */
|
|
322
|
+
--sw-tooltip-bg: #292524;
|
|
323
|
+
--sw-tooltip-text: #fafaf9;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* ===========================================
|
|
327
|
+
Theme: Default (Warm Industrial)
|
|
328
|
+
Distinctive, craft-inspired aesthetic
|
|
329
|
+
=========================================== */
|
|
330
|
+
body.sw-theme-default {
|
|
331
|
+
/* Typography - Source Sans 3: humanist, readable, distinctive */
|
|
332
|
+
--sw-font-display: 'Source Sans 3', system-ui, sans-serif;
|
|
333
|
+
--sw-font-body: 'Source Sans 3', system-ui, sans-serif;
|
|
334
|
+
--sw-font-family: var(--sw-font-body);
|
|
335
|
+
--sw-font-size-base: 17px;
|
|
336
|
+
--sw-font-size-sm: 15px;
|
|
337
|
+
--sw-font-size-lg: 19px;
|
|
338
|
+
--sw-font-size-xl: 24px;
|
|
339
|
+
--sw-line-height: 1.6;
|
|
340
|
+
|
|
341
|
+
/* Colors - High Contrast with Warm Accent
|
|
342
|
+
Amber/brass, not terracotta/orange -- the previous burnt-orange
|
|
343
|
+
(#c2410c) sat close enough to browser validation-error red that
|
|
344
|
+
it read as a warning state on cards rather than a brand accent. */
|
|
345
|
+
--sw-color-primary: #a16207; /* Brass/ochre */
|
|
346
|
+
--sw-color-primary-hover: #854d0e; /* Deeper brass */
|
|
347
|
+
--sw-color-primary-light: #fef3c7; /* Warm pale tint */
|
|
348
|
+
--sw-color-primary-glow: rgba(161, 98, 7, 0.12);
|
|
349
|
+
|
|
350
|
+
/* Colors - High Contrast Neutrals */
|
|
351
|
+
--sw-color-text: #111111; /* Near black - high contrast */
|
|
352
|
+
--sw-color-text-muted: #444444; /* Dark gray - still readable */
|
|
353
|
+
--sw-color-text-light: #888888; /* Placeholder gray */
|
|
354
|
+
--sw-color-bg: #f2ede4; /* Warm light tan -- was #f8f8f8, nearly
|
|
355
|
+
indistinguishable from the white card
|
|
356
|
+
background below; this gives visible
|
|
357
|
+
page/card separation */
|
|
358
|
+
--sw-color-bg-card: #ffffff;
|
|
359
|
+
--sw-color-bg-elevated: #f3f3f3; /* Subtle elevation */
|
|
360
|
+
--sw-color-border: #e0e0e0; /* Clean border */
|
|
361
|
+
--sw-color-border-strong: #cccccc;
|
|
362
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
363
|
+
|
|
364
|
+
/* Colors - Secondary */
|
|
365
|
+
--sw-color-secondary: #333333;
|
|
366
|
+
--sw-color-secondary-hover: #1a1a1a;
|
|
367
|
+
--sw-color-secondary-foreground: #ffffff;
|
|
368
|
+
|
|
369
|
+
/* Colors - Accent (teal for links/highlights) */
|
|
370
|
+
--sw-color-accent: #0d9488;
|
|
371
|
+
--sw-color-accent-light: #e6fffa;
|
|
372
|
+
|
|
373
|
+
/* Spacing -- was "Generous" (1.25/2/3/4rem), measured ~21% taller
|
|
374
|
+
than :doc for identical Dashboard content at the same viewport
|
|
375
|
+
width with no proportional benefit -- tightened to be close to
|
|
376
|
+
:doc's own scale while keeping a touch more room than dashboard
|
|
377
|
+
(which is deliberately the densest theme). */
|
|
378
|
+
--sw-spacing-xs: 0.375rem;
|
|
379
|
+
--sw-spacing-sm: 0.5rem;
|
|
380
|
+
--sw-spacing-md: 1rem;
|
|
381
|
+
--sw-spacing-lg: 1.5rem;
|
|
382
|
+
--sw-spacing-xl: 2rem;
|
|
383
|
+
--sw-spacing-2xl: 3rem;
|
|
384
|
+
|
|
385
|
+
/* Border Radius */
|
|
386
|
+
--sw-radius-sm: 3px;
|
|
387
|
+
--sw-radius-md: 6px;
|
|
388
|
+
--sw-radius-lg: 10px;
|
|
389
|
+
--sw-radius-xl: 16px;
|
|
390
|
+
|
|
391
|
+
/* Shadows - Deep, warm */
|
|
392
|
+
--sw-shadow-sm: 0 1px 2px rgba(28, 25, 23, 0.04), 0 1px 3px rgba(28, 25, 23, 0.06);
|
|
393
|
+
--sw-shadow-md: 0 4px 8px -2px rgba(28, 25, 23, 0.08), 0 2px 4px -1px rgba(28, 25, 23, 0.04);
|
|
394
|
+
--sw-shadow-lg: 0 12px 24px -4px rgba(28, 25, 23, 0.12), 0 4px 8px -2px rgba(28, 25, 23, 0.06);
|
|
395
|
+
--sw-shadow-xl: 0 20px 40px -8px rgba(28, 25, 23, 0.16), 0 8px 16px -4px rgba(28, 25, 23, 0.08);
|
|
396
|
+
--sw-shadow-inner: inset 0 1px 2px rgba(28, 25, 23, 0.06);
|
|
397
|
+
|
|
398
|
+
/* Card styling */
|
|
399
|
+
--sw-card-border-left: 3px solid var(--sw-color-primary);
|
|
400
|
+
|
|
401
|
+
/* Term highlighting */
|
|
402
|
+
--sw-term-color: var(--sw-color-primary);
|
|
403
|
+
--sw-term-bg-hover: var(--sw-color-primary-light);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Default-specific adjustments.
|
|
407
|
+
h1/h3 are already identical to :doc's (both fall through to the
|
|
408
|
+
shared, theme-unaware base rules -- rem against a fixed 16px
|
|
409
|
+
root, not var(--sw-font-size-base)). h2 was the one real gap:
|
|
410
|
+
:doc already has its own tuned override (1.45rem = 23.2px) but
|
|
411
|
+
default never got one, so it fell through to the generic 1.625rem
|
|
412
|
+
(26px) instead -- match :doc's value so both themes share one
|
|
413
|
+
coherent heading scale instead of only mostly agreeing. */
|
|
414
|
+
body.sw-theme-default h2 {
|
|
415
|
+
font-size: 1.45rem;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* ===========================================
|
|
419
|
+
Theme: Dashboard (Data Dense)
|
|
420
|
+
Optimized for data tables, metrics, scanning
|
|
421
|
+
=========================================== */
|
|
422
|
+
body.sw-theme-dashboard {
|
|
423
|
+
/* Typography - Tighter, more compact */
|
|
424
|
+
--sw-font-display: 'Source Sans 3', system-ui, sans-serif;
|
|
425
|
+
--sw-font-body: 'Source Sans 3', system-ui, sans-serif;
|
|
426
|
+
--sw-font-family: var(--sw-font-body);
|
|
427
|
+
--sw-font-size-base: 15px;
|
|
428
|
+
--sw-font-size-sm: 13px;
|
|
429
|
+
--sw-font-size-lg: 17px;
|
|
430
|
+
--sw-font-size-xl: 21px;
|
|
431
|
+
--sw-line-height: 1.5;
|
|
432
|
+
|
|
433
|
+
/* Colors - Same palette, cleaner */
|
|
434
|
+
--sw-color-primary: #a16207;
|
|
435
|
+
--sw-color-primary-hover: #854d0e;
|
|
436
|
+
--sw-color-primary-light: #fef3c7;
|
|
437
|
+
--sw-color-primary-glow: rgba(161, 98, 7, 0.08);
|
|
438
|
+
|
|
439
|
+
--sw-color-text: #111111;
|
|
440
|
+
--sw-color-text-muted: #555555;
|
|
441
|
+
--sw-color-text-light: #888888;
|
|
442
|
+
--sw-color-bg: #fafafa;
|
|
443
|
+
--sw-color-bg-card: #ffffff;
|
|
444
|
+
--sw-color-bg-elevated: #f5f5f5;
|
|
445
|
+
--sw-color-border: #e5e5e5;
|
|
446
|
+
--sw-color-border-strong: #d0d0d0;
|
|
447
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
448
|
+
|
|
449
|
+
--sw-color-secondary: #333333;
|
|
450
|
+
--sw-color-secondary-hover: #1a1a1a;
|
|
451
|
+
--sw-color-secondary-foreground: #ffffff;
|
|
452
|
+
|
|
453
|
+
--sw-color-accent: #0d9488;
|
|
454
|
+
--sw-color-accent-light: #e6fffa;
|
|
455
|
+
|
|
456
|
+
/* Spacing - Reduced for density */
|
|
457
|
+
--sw-spacing-xs: 0.3125rem;
|
|
458
|
+
--sw-spacing-sm: 0.4375rem;
|
|
459
|
+
--sw-spacing-md: 0.75rem;
|
|
460
|
+
--sw-spacing-lg: 1.125rem;
|
|
461
|
+
--sw-spacing-xl: 1.5rem;
|
|
462
|
+
--sw-spacing-2xl: 2.25rem;
|
|
463
|
+
|
|
464
|
+
/* Table/callout density -- tighter than the 0.75rem/1rem shared default,
|
|
465
|
+
since "Data Dense" is this theme's whole reason to exist */
|
|
466
|
+
--sw-table-header-padding: 6px 10px;
|
|
467
|
+
--sw-table-cell-padding: 7px 10px;
|
|
468
|
+
--sw-callout-padding: 10px 14px;
|
|
469
|
+
|
|
470
|
+
/* Border Radius - Slightly smaller */
|
|
471
|
+
--sw-radius-sm: 2px;
|
|
472
|
+
--sw-radius-md: 4px;
|
|
473
|
+
--sw-radius-lg: 6px;
|
|
474
|
+
--sw-radius-xl: 10px;
|
|
475
|
+
|
|
476
|
+
/* Shadows - Minimal */
|
|
477
|
+
--sw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
478
|
+
--sw-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.06);
|
|
479
|
+
--sw-shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.08);
|
|
480
|
+
--sw-shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.1);
|
|
481
|
+
--sw-shadow-inner: inset 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
482
|
+
|
|
483
|
+
/* Card styling - No accent bar */
|
|
484
|
+
--sw-card-border-left: 1px solid var(--sw-color-border);
|
|
485
|
+
|
|
486
|
+
--sw-term-color: var(--sw-color-primary);
|
|
487
|
+
--sw-term-bg-hover: var(--sw-color-primary-light);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* Dashboard-specific adjustments */
|
|
491
|
+
body.sw-theme-dashboard h2 {
|
|
492
|
+
font-size: 1.25rem;
|
|
493
|
+
margin-top: var(--sw-spacing-lg);
|
|
494
|
+
margin-bottom: var(--sw-spacing-sm);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
body.sw-theme-dashboard .score-table th,
|
|
498
|
+
body.sw-theme-dashboard .score-table td {
|
|
499
|
+
padding: 6px 10px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* ===========================================
|
|
503
|
+
Theme: Document (Reading Optimized)
|
|
504
|
+
Long-form reading, focus mode, elegant
|
|
505
|
+
=========================================== */
|
|
506
|
+
body.sw-theme-document {
|
|
507
|
+
/* Typography - Serif, larger, generous line height */
|
|
508
|
+
--sw-font-display: 'Source Sans 3', system-ui, sans-serif;
|
|
509
|
+
--sw-font-body: 'Crimson Pro', Georgia, 'Times New Roman', serif;
|
|
510
|
+
--sw-font-family: var(--sw-font-body);
|
|
511
|
+
--sw-font-size-base: 19px;
|
|
512
|
+
--sw-font-size-sm: 16px;
|
|
513
|
+
--sw-font-size-lg: 22px;
|
|
514
|
+
--sw-font-size-xl: 28px;
|
|
515
|
+
--sw-line-height: 1.85;
|
|
516
|
+
|
|
517
|
+
/* Colors - Warm paper tones */
|
|
518
|
+
--sw-color-primary: #6b7280; /* Muted gray-blue */
|
|
519
|
+
--sw-color-primary-hover: #4b5563;
|
|
520
|
+
--sw-color-primary-light: #f3f4f6;
|
|
521
|
+
--sw-color-primary-glow: rgba(107, 114, 128, 0.1);
|
|
522
|
+
|
|
523
|
+
--sw-color-text: #1a1a1a;
|
|
524
|
+
--sw-color-text-muted: #4a4a4a;
|
|
525
|
+
--sw-color-text-light: #7a7a7a;
|
|
526
|
+
--sw-color-bg: #faf8f5; /* Warm paper */
|
|
527
|
+
--sw-color-bg-card: #ffffff;
|
|
528
|
+
--sw-color-bg-elevated: #f5f3f0;
|
|
529
|
+
--sw-color-border: #e8e4df;
|
|
530
|
+
--sw-color-border-strong: #d8d4cf;
|
|
531
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
532
|
+
|
|
533
|
+
--sw-color-secondary: #4a4a4a;
|
|
534
|
+
--sw-color-secondary-hover: #2a2a2a;
|
|
535
|
+
--sw-color-secondary-foreground: #ffffff;
|
|
536
|
+
|
|
537
|
+
--sw-color-accent: #2563eb;
|
|
538
|
+
--sw-color-accent-light: #eff6ff;
|
|
539
|
+
|
|
540
|
+
/* Spacing - More generous for reading */
|
|
541
|
+
--sw-spacing-xs: 0.5rem;
|
|
542
|
+
--sw-spacing-sm: 0.875rem;
|
|
543
|
+
--sw-spacing-md: 1.5rem;
|
|
544
|
+
--sw-spacing-lg: 2.5rem;
|
|
545
|
+
--sw-spacing-xl: 4rem;
|
|
546
|
+
--sw-spacing-2xl: 5rem;
|
|
547
|
+
|
|
548
|
+
/* Border Radius - Softer */
|
|
549
|
+
--sw-radius-sm: 4px;
|
|
550
|
+
--sw-radius-md: 8px;
|
|
551
|
+
--sw-radius-lg: 12px;
|
|
552
|
+
--sw-radius-xl: 20px;
|
|
553
|
+
|
|
554
|
+
/* Shadows - Very subtle */
|
|
555
|
+
--sw-shadow-sm: none;
|
|
556
|
+
--sw-shadow-md: 0 1px 3px rgba(0, 0, 0, 0.04);
|
|
557
|
+
--sw-shadow-lg: 0 2px 6px rgba(0, 0, 0, 0.06);
|
|
558
|
+
--sw-shadow-xl: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
559
|
+
--sw-shadow-inner: none;
|
|
560
|
+
|
|
561
|
+
/* Card styling - No accent, subtle */
|
|
562
|
+
--sw-card-border-left: none;
|
|
563
|
+
|
|
564
|
+
--sw-term-color: var(--sw-color-accent);
|
|
565
|
+
--sw-term-bg-hover: var(--sw-color-accent-light);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* Document-specific adjustments
|
|
569
|
+
The 720px cap is a prose reading-width, so it's scoped off of
|
|
570
|
+
body:not(:has(.sw-app-shell)) -- an app_shell page manages its
|
|
571
|
+
own width via its sidebar+main grid, and capping the whole body
|
|
572
|
+
at 720px squeezes that grid down to a sliver (e.g. two 1fr chart
|
|
573
|
+
columns collapsing to ~180px each instead of filling the content
|
|
574
|
+
pane), well below what a plain document's max-width shouldn't
|
|
575
|
+
ever apply to. */
|
|
576
|
+
body.sw-theme-document:not(:has(.sw-app-shell)) {
|
|
577
|
+
max-width: 720px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* #app-container re-applies --sw-spacing-xl as its own padding on top
|
|
581
|
+
of body's --sw-spacing-xl padding (see the shared `body` and
|
|
582
|
+
`#app-container` rules above). Document theme's --sw-spacing-xl
|
|
583
|
+
(4rem) is the largest of any theme, so that doubling costs 128px
|
|
584
|
+
of width off the top -- fine for prose, but enough to squeeze an
|
|
585
|
+
app_shell's sidebar+main grid down until 2-column chart layouts
|
|
586
|
+
lose bars/points to an undersized canvas. Halve the inner layer
|
|
587
|
+
only in the app_shell case; the outer body padding alone still
|
|
588
|
+
gives comfortable page margins. */
|
|
589
|
+
body.sw-theme-document:has(.sw-app-shell) #app-container {
|
|
590
|
+
padding: calc(var(--sw-spacing-xl) / 2);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
body.sw-theme-document p {
|
|
594
|
+
margin-bottom: 1.5em;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
body.sw-theme-document h1, body.sw-theme-document h2,
|
|
598
|
+
body.sw-theme-document h3, body.sw-theme-document h4 {
|
|
599
|
+
font-family: var(--sw-font-display);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
body.sw-theme-document h2 {
|
|
603
|
+
margin-top: 3rem;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
body.sw-theme-document .card {
|
|
607
|
+
border-left: none;
|
|
608
|
+
border: 1px solid var(--sw-color-border);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* ===========================================
|
|
612
|
+
Theme: Doc (Anthropic-artifact editorial)
|
|
613
|
+
Compact editorial — Charter serif headers, system sans body
|
|
614
|
+
=========================================== */
|
|
615
|
+
body.sw-theme-doc {
|
|
616
|
+
--sw-font-display: Charter, 'Bitstream Charter', 'Sitka Text', Cambria, Georgia, serif;
|
|
617
|
+
--sw-font-body: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
|
|
618
|
+
--sw-font-family: var(--sw-font-body);
|
|
619
|
+
--sw-font-mono: 'SFMono-Regular', 'SF Mono', 'Cascadia Code', Menlo, 'Courier New', monospace;
|
|
620
|
+
--sw-font-size-base: 15px;
|
|
621
|
+
--sw-font-size-sm: 13px;
|
|
622
|
+
--sw-font-size-lg: 17px;
|
|
623
|
+
--sw-font-size-xl: 22px;
|
|
624
|
+
--sw-line-height: 1.65;
|
|
625
|
+
|
|
626
|
+
/* Colors — artifact-exact palette */
|
|
627
|
+
--sw-color-primary: #1E4ED8;
|
|
628
|
+
--sw-color-primary-hover: #1a3fb0;
|
|
629
|
+
--sw-color-primary-light: #EEF2FF;
|
|
630
|
+
--sw-color-primary-glow: rgba(30, 78, 216, 0.1);
|
|
631
|
+
|
|
632
|
+
--sw-color-text: #181714;
|
|
633
|
+
--sw-color-text-muted: #6B6860;
|
|
634
|
+
--sw-color-text-light: #A09D96;
|
|
635
|
+
--sw-color-bg: #F5F4EF;
|
|
636
|
+
--sw-color-bg-card: #EDECE6;
|
|
637
|
+
--sw-color-bg-elevated: #ECEAE3;
|
|
638
|
+
--sw-color-border: #E0DED6;
|
|
639
|
+
--sw-color-border-strong: #C8C6BE;
|
|
640
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
641
|
+
|
|
642
|
+
--sw-color-secondary: #6B6860;
|
|
643
|
+
--sw-color-secondary-hover: #4a4845;
|
|
644
|
+
--sw-color-secondary-foreground: #ffffff;
|
|
645
|
+
|
|
646
|
+
--sw-color-accent: #1E4ED8;
|
|
647
|
+
--sw-color-accent-light: #EEF2FF;
|
|
648
|
+
|
|
649
|
+
/* Spacing — compact editorial */
|
|
650
|
+
--sw-spacing-xs: 0.25rem;
|
|
651
|
+
--sw-spacing-sm: 0.5rem;
|
|
652
|
+
--sw-spacing-md: 1rem;
|
|
653
|
+
--sw-spacing-lg: 1.5rem;
|
|
654
|
+
--sw-spacing-xl: 2rem;
|
|
655
|
+
--sw-spacing-2xl: 3.25rem;
|
|
656
|
+
|
|
657
|
+
/* Border Radius — minimal */
|
|
658
|
+
--sw-radius-sm: 2px;
|
|
659
|
+
--sw-radius-md: 4px;
|
|
660
|
+
--sw-radius-lg: 6px;
|
|
661
|
+
--sw-radius-xl: 8px;
|
|
662
|
+
|
|
663
|
+
/* Shadows — near-flat */
|
|
664
|
+
--sw-shadow-sm: none;
|
|
665
|
+
--sw-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
666
|
+
--sw-shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.06);
|
|
667
|
+
--sw-shadow-xl: 0 4px 8px rgba(0, 0, 0, 0.08);
|
|
668
|
+
--sw-shadow-inner: none;
|
|
669
|
+
|
|
670
|
+
/* Card — no accent left border */
|
|
671
|
+
--sw-card-border-left: none;
|
|
672
|
+
|
|
673
|
+
/* Shared-component density tokens — let render_table/callout_css read
|
|
674
|
+
theme-owned values via normal CSS cascade instead of !important */
|
|
675
|
+
--sw-table-header-padding: 8px 12px;
|
|
676
|
+
--sw-table-cell-padding: 9px 12px;
|
|
677
|
+
--sw-callout-padding: 14px 16px;
|
|
678
|
+
|
|
679
|
+
--sw-term-color: var(--sw-color-accent);
|
|
680
|
+
--sw-term-bg-hover: var(--sw-color-accent-light);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* Doc theme — dark mode overrides (data-sw-theme="dark" lives on <html>).
|
|
684
|
+
Colors only — type/spacing/radius/shadow cascade from the light block above. */
|
|
685
|
+
html[data-sw-theme="dark"] body.sw-theme-doc {
|
|
686
|
+
--sw-color-primary: #6699FF;
|
|
687
|
+
--sw-color-primary-hover: #8AB0FF;
|
|
688
|
+
--sw-color-primary-light: #1B2740;
|
|
689
|
+
--sw-color-primary-glow: rgba(102, 153, 255, 0.15);
|
|
690
|
+
|
|
691
|
+
--sw-color-text: #ECEAE3;
|
|
692
|
+
--sw-color-text-muted: #A8A399;
|
|
693
|
+
--sw-color-text-light: #716C61;
|
|
694
|
+
--sw-color-bg: #1A1714;
|
|
695
|
+
--sw-color-bg-card: #232019;
|
|
696
|
+
--sw-color-bg-elevated: #2A251F;
|
|
697
|
+
--sw-color-border: #3A352D;
|
|
698
|
+
--sw-color-border-strong: #4A453B;
|
|
699
|
+
--sw-color-border-focus: var(--sw-color-primary);
|
|
700
|
+
|
|
701
|
+
--sw-color-secondary: #A8A399;
|
|
702
|
+
--sw-color-secondary-hover: #C5C0B5;
|
|
703
|
+
--sw-color-secondary-foreground: #1A1714;
|
|
704
|
+
|
|
705
|
+
--sw-color-accent: #6699FF;
|
|
706
|
+
--sw-color-accent-light: #1B2740;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/* Doc theme — serif section headers at artifact-exact size */
|
|
710
|
+
body.sw-theme-doc h2 {
|
|
711
|
+
font-family: var(--sw-font-display);
|
|
712
|
+
font-size: 1.45rem;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/* Doc apps render flat component lists (no <section> wrappers) —
|
|
716
|
+
the section boundary marker is .sw-doc-section-header, so the
|
|
717
|
+
artifact's section { margin-bottom: 52px } becomes margin-top here.
|
|
718
|
+
Adjacent-sibling margin collapsing yields max(prev, 52px) = 52px,
|
|
719
|
+
same effective gap as the artifact. */
|
|
720
|
+
body.sw-theme-doc .sw-doc-section-header {
|
|
721
|
+
margin-top: 52px;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/* render_table/callout_css now read --sw-table-header-padding /
|
|
725
|
+
--sw-table-cell-padding / --sw-callout-padding (set in the doc theme
|
|
726
|
+
variable block above) via normal CSS custom-property cascade — no
|
|
727
|
+
!important needed since it's no longer a specificity fight, just a
|
|
728
|
+
token lookup. Remaining rules here are cosmetic details the token
|
|
729
|
+
system doesn't cover yet. */
|
|
730
|
+
body.sw-theme-doc .sw-table {
|
|
731
|
+
font-size: .875rem;
|
|
732
|
+
}
|
|
733
|
+
body.sw-theme-doc .sw-table th {
|
|
734
|
+
font-size: 11px;
|
|
735
|
+
}
|
|
736
|
+
body.sw-theme-doc .sw-table td:first-child {
|
|
737
|
+
white-space: nowrap;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/* Doc theme — code blocks always render in the dark
|
|
741
|
+
reverse-video scheme (Forrest, live UAT screenshot,
|
|
742
|
+
2026-09-03), regardless of the page's own light/dark
|
|
743
|
+
toggle: unlike the rest of the doc theme (which tracks
|
|
744
|
+
light/dark via --sw-color-bg-card etc.), a code block's
|
|
745
|
+
own background is pinned to these tokens in BOTH modes.
|
|
746
|
+
`.sw-code-block` normally reads --sw-surface, which IS
|
|
747
|
+
bridged to --sw-color-bg-card on body (theme.rb) -- so
|
|
748
|
+
the prior symptom wasn't a raw #ffffff fallback, it was
|
|
749
|
+
the doc theme's own warm near-white card colour showing
|
|
750
|
+
through a wrapper Prism's tomorrow-theme token colours
|
|
751
|
+
(loaded for every code_block, adapter/alpinejs.rb
|
|
752
|
+
inject_prism_cdn) assume is dark. Scoped to
|
|
753
|
+
.sw-theme-doc only -- every other theme's code blocks
|
|
754
|
+
are untouched. */
|
|
755
|
+
body.sw-theme-doc {
|
|
756
|
+
--sw-doc-code-bg: #1e1e1e;
|
|
757
|
+
--sw-doc-code-bg-elevated: #252526;
|
|
758
|
+
--sw-doc-code-border: #333333;
|
|
759
|
+
--sw-doc-code-text: #d4d4d4;
|
|
760
|
+
--sw-doc-code-text-dim: #9a9a9a;
|
|
761
|
+
}
|
|
762
|
+
body.sw-theme-doc .sw-code-block {
|
|
763
|
+
background: var(--sw-doc-code-bg);
|
|
764
|
+
border-color: var(--sw-doc-code-border);
|
|
765
|
+
}
|
|
766
|
+
body.sw-theme-doc .sw-code-block__header {
|
|
767
|
+
background: var(--sw-doc-code-bg-elevated);
|
|
768
|
+
border-color: var(--sw-doc-code-border);
|
|
769
|
+
color: var(--sw-doc-code-text-dim);
|
|
770
|
+
}
|
|
771
|
+
body.sw-theme-doc .sw-code-block__copy {
|
|
772
|
+
background: #2d2d2d;
|
|
773
|
+
border-color: #444444;
|
|
774
|
+
color: var(--sw-doc-code-text);
|
|
775
|
+
}
|
|
776
|
+
body.sw-theme-doc .sw-code-block__copy:hover {
|
|
777
|
+
background: #3a3a3a;
|
|
778
|
+
}
|
|
779
|
+
body.sw-theme-doc .sw-code-block__pre,
|
|
780
|
+
body.sw-theme-doc .sw-code-block__pre code {
|
|
781
|
+
color: var(--sw-doc-code-text);
|
|
782
|
+
}
|
|
783
|
+
body.sw-theme-doc .sw-code-block__truncated {
|
|
784
|
+
background: var(--sw-doc-code-bg-elevated);
|
|
785
|
+
border-color: var(--sw-doc-code-border);
|
|
786
|
+
color: var(--sw-doc-code-text-dim);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* ===========================================
|
|
790
|
+
Base Styles
|
|
791
|
+
=========================================== */
|
|
792
|
+
*, *::before, *::after {
|
|
793
|
+
box-sizing: border-box;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/* Allow overflow with scrollbar when needed */
|
|
797
|
+
html {
|
|
798
|
+
overflow-x: auto;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
body {
|
|
802
|
+
font-family: var(--sw-font-body);
|
|
803
|
+
font-size: var(--sw-font-size-base);
|
|
804
|
+
line-height: var(--sw-line-height);
|
|
805
|
+
color: var(--sw-color-text);
|
|
806
|
+
margin: 0;
|
|
807
|
+
padding: 0;
|
|
808
|
+
background: var(--sw-color-bg);
|
|
809
|
+
-webkit-font-smoothing: antialiased;
|
|
810
|
+
-moz-osx-font-smoothing: grayscale;
|
|
811
|
+
min-height: 100vh;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Layout modes */
|
|
815
|
+
body[class*="sw-layout-"] { margin: 0 auto; padding: var(--sw-spacing-xl); }
|
|
816
|
+
body.sw-layout-default { max-width: 900px; }
|
|
817
|
+
body.sw-layout-wide { max-width: 1100px; }
|
|
818
|
+
body.sw-layout-full { max-width: 1400px; }
|
|
819
|
+
body.sw-layout-fluid { max-width: 100%; padding-left: var(--sw-spacing-xl); padding-right: var(--sw-spacing-xl); }
|
|
820
|
+
|
|
821
|
+
/* chrome: false gets a minimal padding baseline so content isn't
|
|
822
|
+
glued to the viewport edge -- still just one low-specificity
|
|
823
|
+
class rule, easily overridden to zero by an app's own CSS
|
|
824
|
+
(FAC-9u2). */
|
|
825
|
+
body.sw-chromeless { padding: var(--sw-spacing-lg); }
|
|
826
|
+
|
|
827
|
+
/* Page title outside container */
|
|
828
|
+
body > h1 {
|
|
829
|
+
font-size: 2.5rem;
|
|
830
|
+
font-weight: 700;
|
|
831
|
+
color: var(--sw-color-text);
|
|
832
|
+
letter-spacing: -0.02em;
|
|
833
|
+
margin: 0 0 var(--sw-spacing-lg) 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
body[class*="sw-layout-"] > #app-container {
|
|
837
|
+
background: var(--sw-color-bg-card);
|
|
838
|
+
padding: var(--sw-spacing-xl);
|
|
839
|
+
border-radius: var(--sw-radius-lg);
|
|
840
|
+
box-shadow: var(--sw-shadow-md);
|
|
841
|
+
border: 1px solid var(--sw-color-border);
|
|
842
|
+
overflow-x: auto;
|
|
843
|
+
word-wrap: break-word;
|
|
844
|
+
overflow-wrap: break-word;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* ===========================================
|
|
848
|
+
Typography - Clean, Scannable Hierarchy
|
|
849
|
+
=========================================== */
|
|
850
|
+
h1, h2, h3, h4, h5, h6 {
|
|
851
|
+
font-family: var(--sw-font-body);
|
|
852
|
+
font-weight: 600;
|
|
853
|
+
letter-spacing: -0.01em;
|
|
854
|
+
color: var(--sw-color-text);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
h1 {
|
|
858
|
+
font-size: 2.25rem;
|
|
859
|
+
font-weight: 700;
|
|
860
|
+
margin-top: 0;
|
|
861
|
+
margin-bottom: var(--sw-spacing-lg);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
h2 {
|
|
865
|
+
font-size: 1.625rem;
|
|
866
|
+
font-weight: 600;
|
|
867
|
+
margin-top: var(--sw-spacing-xl);
|
|
868
|
+
margin-bottom: var(--sw-spacing-md);
|
|
869
|
+
padding-bottom: var(--sw-spacing-sm);
|
|
870
|
+
border-bottom: 2px solid var(--sw-color-border);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
h3 {
|
|
874
|
+
font-size: 1.25rem;
|
|
875
|
+
font-weight: 600;
|
|
876
|
+
margin-top: var(--sw-spacing-lg);
|
|
877
|
+
margin-bottom: var(--sw-spacing-sm);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
h4 {
|
|
881
|
+
font-size: 1.2rem;
|
|
882
|
+
font-weight: 700;
|
|
883
|
+
margin-top: var(--sw-spacing-md);
|
|
884
|
+
margin-bottom: var(--sw-spacing-xs);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
h5 {
|
|
888
|
+
font-size: 1.1rem;
|
|
889
|
+
font-weight: 700;
|
|
890
|
+
margin-top: var(--sw-spacing-md);
|
|
891
|
+
margin-bottom: var(--sw-spacing-xs);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
h6 {
|
|
895
|
+
font-size: 1rem;
|
|
896
|
+
font-weight: 700;
|
|
897
|
+
text-transform: uppercase;
|
|
898
|
+
letter-spacing: 0.05em;
|
|
899
|
+
color: var(--sw-color-text-muted);
|
|
900
|
+
margin-top: var(--sw-spacing-md);
|
|
901
|
+
margin-bottom: var(--sw-spacing-xs);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
p {
|
|
905
|
+
color: var(--sw-color-text-muted);
|
|
906
|
+
line-height: var(--sw-line-height);
|
|
907
|
+
margin: 0 0 var(--sw-spacing-md) 0;
|
|
908
|
+
word-wrap: break-word;
|
|
909
|
+
overflow-wrap: break-word;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
ul, ol {
|
|
913
|
+
margin: 0 0 var(--sw-spacing-md) 1.25rem;
|
|
914
|
+
color: var(--sw-color-text-muted);
|
|
915
|
+
line-height: var(--sw-line-height);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
li {
|
|
919
|
+
margin-bottom: 0.3125rem;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
li:last-child {
|
|
923
|
+
margin-bottom: 0;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/* Handle long URLs and strings */
|
|
927
|
+
a, code, pre {
|
|
928
|
+
word-wrap: break-word;
|
|
929
|
+
overflow-wrap: break-word;
|
|
930
|
+
word-break: break-word;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* Inline code -- monospace fonts render optically larger than body
|
|
934
|
+
text at the same declared size, so scale down to match */
|
|
935
|
+
code {
|
|
936
|
+
font-size: .825em;
|
|
937
|
+
background: var(--sw-surface-elevated, #ECEAE3);
|
|
938
|
+
padding: 1px 5px;
|
|
939
|
+
border-radius: 3px;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
pre code, .sw-code-block__pre code {
|
|
943
|
+
font-size: inherit;
|
|
944
|
+
background: none;
|
|
945
|
+
padding: 0;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/* Strong text */
|
|
949
|
+
strong, b {
|
|
950
|
+
font-weight: 600;
|
|
951
|
+
color: var(--sw-color-text);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
p + input, p + select, p + textarea {
|
|
955
|
+
margin-top: var(--sw-spacing-xs);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
p:has(+ input), p:has(+ select), p:has(+ textarea) {
|
|
959
|
+
margin-bottom: var(--sw-spacing-xs);
|
|
960
|
+
font-weight: 500;
|
|
961
|
+
color: var(--sw-color-text);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/* ===========================================
|
|
965
|
+
Form Controls - Refined Inputs
|
|
966
|
+
=========================================== */
|
|
967
|
+
input[type="text"], input[type="email"], select, textarea {
|
|
968
|
+
padding: var(--sw-spacing-md);
|
|
969
|
+
margin: var(--sw-spacing-sm) 0 var(--sw-spacing-md) 0;
|
|
970
|
+
border: 1px solid var(--input);
|
|
971
|
+
border-radius: var(--radius);
|
|
972
|
+
font-size: var(--sw-font-size-base);
|
|
973
|
+
font-family: var(--sw-font-body);
|
|
974
|
+
width: 100%;
|
|
975
|
+
background: var(--background);
|
|
976
|
+
color: var(--foreground);
|
|
977
|
+
box-shadow: var(--sw-shadow-inner);
|
|
978
|
+
transition:
|
|
979
|
+
border-color var(--sw-transition),
|
|
980
|
+
box-shadow var(--sw-transition),
|
|
981
|
+
background-color var(--sw-transition);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
input[type="text"]:hover, input[type="email"]:hover, select:hover, textarea:hover {
|
|
985
|
+
border-color: var(--sw-color-border-strong);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
input[type="text"]:focus, input[type="email"]:focus, select:focus, textarea:focus {
|
|
989
|
+
outline: none;
|
|
990
|
+
border-color: var(--ring);
|
|
991
|
+
box-shadow:
|
|
992
|
+
0 0 0 2px var(--background),
|
|
993
|
+
0 0 0 4px var(--ring);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
input[type="text"]::placeholder, input[type="email"]::placeholder, textarea::placeholder {
|
|
997
|
+
color: var(--muted-foreground);
|
|
998
|
+
font-style: italic;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
textarea {
|
|
1002
|
+
resize: vertical;
|
|
1003
|
+
min-height: 100px;
|
|
1004
|
+
line-height: 1.5;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/* Visible label wrapper for text_field/text_area/select/chip_group
|
|
1008
|
+
(FAC-9u2 -- these silently dropped label: before). */
|
|
1009
|
+
.sw-field {
|
|
1010
|
+
display: flex;
|
|
1011
|
+
flex-direction: column;
|
|
1012
|
+
gap: var(--sw-spacing-xs);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
.sw-field__label {
|
|
1016
|
+
font-size: var(--sw-font-size-sm);
|
|
1017
|
+
font-weight: 600;
|
|
1018
|
+
color: var(--sw-color-text);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.sw-field input, .sw-field select, .sw-field textarea, .sw-field .sw-chip-group {
|
|
1022
|
+
margin-top: 0;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/* ===========================================
|
|
1026
|
+
App Header - Full-width header bar
|
|
1027
|
+
=========================================== */
|
|
1028
|
+
.sw-app-header {
|
|
1029
|
+
display: flex;
|
|
1030
|
+
justify-content: space-between;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
padding: var(--sw-spacing-md) var(--sw-spacing-lg);
|
|
1033
|
+
margin: calc(-1 * var(--sw-spacing-xl)) calc(-1 * var(--sw-spacing-xl)) var(--sw-spacing-lg) calc(-1 * var(--sw-spacing-xl));
|
|
1034
|
+
font-family: var(--sw-font-body);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
.sw-app-header-brand {
|
|
1038
|
+
display: flex;
|
|
1039
|
+
align-items: baseline;
|
|
1040
|
+
gap: var(--sw-spacing-sm);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.sw-app-header-title {
|
|
1044
|
+
font-size: var(--sw-font-size-lg);
|
|
1045
|
+
font-weight: 600;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.sw-app-header-subtitle {
|
|
1049
|
+
font-size: var(--sw-font-size-sm);
|
|
1050
|
+
opacity: 0.7;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.sw-app-header-actions {
|
|
1054
|
+
display: flex;
|
|
1055
|
+
align-items: center;
|
|
1056
|
+
gap: var(--sw-spacing-sm);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/* Header variants */
|
|
1060
|
+
.sw-app-header-dark {
|
|
1061
|
+
background: var(--card);
|
|
1062
|
+
color: var(--card-foreground);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
.sw-app-header-dark .sw-app-header-subtitle {
|
|
1066
|
+
color: var(--muted-foreground);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.sw-app-header-dark button,
|
|
1070
|
+
.sw-app-header-dark .btn {
|
|
1071
|
+
background: transparent;
|
|
1072
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
1073
|
+
color: #ffffff;
|
|
1074
|
+
margin: 0;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.sw-app-header-dark button:hover,
|
|
1078
|
+
.sw-app-header-dark .btn:hover {
|
|
1079
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1080
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.sw-app-header-light {
|
|
1084
|
+
background: var(--sw-color-bg-elevated);
|
|
1085
|
+
color: var(--sw-color-text);
|
|
1086
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.sw-app-header-primary {
|
|
1090
|
+
background: var(--sw-color-primary);
|
|
1091
|
+
color: #ffffff;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
.sw-app-header-primary button,
|
|
1095
|
+
.sw-app-header-primary .btn {
|
|
1096
|
+
background: rgba(255, 255, 255, 0.2);
|
|
1097
|
+
border: none;
|
|
1098
|
+
color: #ffffff;
|
|
1099
|
+
margin: 0;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.sw-app-header-primary button:hover,
|
|
1103
|
+
.sw-app-header-primary .btn:hover {
|
|
1104
|
+
background: rgba(255, 255, 255, 0.3);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/* ===========================================
|
|
1108
|
+
Buttons - Bold, Confident
|
|
1109
|
+
=========================================== */
|
|
1110
|
+
button, .btn {
|
|
1111
|
+
display: inline-flex;
|
|
1112
|
+
align-items: center;
|
|
1113
|
+
justify-content: center;
|
|
1114
|
+
gap: var(--sw-spacing-xs);
|
|
1115
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
1116
|
+
margin: var(--sw-spacing-sm) var(--sw-spacing-sm) var(--sw-spacing-sm) 0;
|
|
1117
|
+
border: none;
|
|
1118
|
+
border-radius: var(--radius);
|
|
1119
|
+
font-family: var(--sw-font-body);
|
|
1120
|
+
font-size: var(--sw-font-size-sm);
|
|
1121
|
+
font-weight: 600;
|
|
1122
|
+
letter-spacing: 0.01em;
|
|
1123
|
+
cursor: pointer;
|
|
1124
|
+
white-space: nowrap;
|
|
1125
|
+
transition:
|
|
1126
|
+
background var(--sw-transition),
|
|
1127
|
+
color var(--sw-transition),
|
|
1128
|
+
transform var(--sw-transition-fast),
|
|
1129
|
+
box-shadow var(--sw-transition);
|
|
1130
|
+
position: relative;
|
|
1131
|
+
overflow: hidden;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
button:hover {
|
|
1135
|
+
transform: translateY(-1px);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
button:active {
|
|
1139
|
+
transform: translateY(0) scale(0.98);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* Primary */
|
|
1143
|
+
.btn-primary {
|
|
1144
|
+
background: var(--primary);
|
|
1145
|
+
color: var(--primary-foreground);
|
|
1146
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.btn-primary:hover {
|
|
1150
|
+
opacity: 0.9;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/* Secondary */
|
|
1154
|
+
.btn-secondary {
|
|
1155
|
+
background: var(--secondary);
|
|
1156
|
+
color: var(--secondary-foreground);
|
|
1157
|
+
border: 1px solid var(--border);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.btn-secondary:hover {
|
|
1161
|
+
opacity: 0.85;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/* Destructive */
|
|
1165
|
+
.btn-destructive {
|
|
1166
|
+
background: var(--destructive);
|
|
1167
|
+
color: var(--destructive-foreground);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.btn-destructive:hover {
|
|
1171
|
+
opacity: 0.9;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/* Outline */
|
|
1175
|
+
.btn-outline {
|
|
1176
|
+
background: transparent;
|
|
1177
|
+
color: var(--foreground);
|
|
1178
|
+
border: 1px solid var(--border);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.btn-outline:hover {
|
|
1182
|
+
background: var(--accent);
|
|
1183
|
+
color: var(--accent-foreground);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/* Ghost */
|
|
1187
|
+
.btn-ghost {
|
|
1188
|
+
background: transparent;
|
|
1189
|
+
color: var(--foreground);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.btn-ghost:hover {
|
|
1193
|
+
background: var(--accent);
|
|
1194
|
+
color: var(--accent-foreground);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/* Quiet -- minimal chrome, for inline row/table actions (rivet grammar) */
|
|
1198
|
+
.btn-quiet {
|
|
1199
|
+
background: transparent;
|
|
1200
|
+
box-shadow: none;
|
|
1201
|
+
color: var(--foreground);
|
|
1202
|
+
font-weight: 500;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.btn-quiet:hover {
|
|
1206
|
+
background: var(--accent);
|
|
1207
|
+
color: var(--accent-foreground);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/* Size: sm -- compact, for inline table actions */
|
|
1211
|
+
.btn-sm {
|
|
1212
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
1213
|
+
font-size: var(--sw-font-size-sm);
|
|
1214
|
+
font-weight: 500;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/* Table action cells: inline row of buttons instead of stacked (FAC-9u2) */
|
|
1218
|
+
.sw-table__actions {
|
|
1219
|
+
display: flex;
|
|
1220
|
+
align-items: center;
|
|
1221
|
+
gap: var(--sw-spacing-xs);
|
|
1222
|
+
flex-wrap: wrap;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/* Button focus states */
|
|
1226
|
+
button:focus-visible {
|
|
1227
|
+
outline: none;
|
|
1228
|
+
box-shadow:
|
|
1229
|
+
0 0 0 2px var(--background),
|
|
1230
|
+
0 0 0 4px var(--ring);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/* Button loading state (HTMX hx-disabled-elt or manual disable) */
|
|
1234
|
+
button:disabled, button.htmx-request {
|
|
1235
|
+
opacity: 0.65;
|
|
1236
|
+
cursor: not-allowed;
|
|
1237
|
+
transform: none;
|
|
1238
|
+
pointer-events: none;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
button:disabled::after, button.htmx-request:not(.sw-no-loading-indicator)::after {
|
|
1242
|
+
content: "";
|
|
1243
|
+
position: absolute;
|
|
1244
|
+
top: 50%;
|
|
1245
|
+
left: 50%;
|
|
1246
|
+
width: 0.85em;
|
|
1247
|
+
height: 0.85em;
|
|
1248
|
+
margin-top: -0.425em;
|
|
1249
|
+
margin-left: -0.425em;
|
|
1250
|
+
border: 2px solid currentColor;
|
|
1251
|
+
border-top-color: transparent;
|
|
1252
|
+
border-radius: 50%;
|
|
1253
|
+
opacity: 0;
|
|
1254
|
+
animation:
|
|
1255
|
+
sw-btn-spinner-appear 0ms 300ms forwards,
|
|
1256
|
+
sw-spin 0.7s 300ms linear infinite;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
@keyframes sw-btn-spinner-appear {
|
|
1260
|
+
to { opacity: 1; }
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/* Swap-target busy treatment during in-flight requests (FAC-P1.5).
|
|
1264
|
+
Delay-in via transition-delay so fast responses never flicker;
|
|
1265
|
+
undimming (class removed) is immediate. */
|
|
1266
|
+
#app-container {
|
|
1267
|
+
transition: opacity var(--sw-transition-fast, 120ms) ease-out;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
#app-container.htmx-request {
|
|
1271
|
+
opacity: 0.85;
|
|
1272
|
+
transition-delay: 150ms;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* ===========================================
|
|
1276
|
+
Checkbox & Labels
|
|
1277
|
+
=========================================== */
|
|
1278
|
+
label {
|
|
1279
|
+
display: flex;
|
|
1280
|
+
align-items: center;
|
|
1281
|
+
margin: var(--sw-spacing-md) 0;
|
|
1282
|
+
cursor: pointer;
|
|
1283
|
+
user-select: none;
|
|
1284
|
+
color: var(--sw-color-text);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
input[type="checkbox"] {
|
|
1288
|
+
margin-right: var(--sw-spacing-sm);
|
|
1289
|
+
width: 18px;
|
|
1290
|
+
height: 18px;
|
|
1291
|
+
cursor: pointer;
|
|
1292
|
+
accent-color: var(--sw-color-primary);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
select {
|
|
1296
|
+
cursor: pointer;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/* ===========================================
|
|
1300
|
+
Radio Group
|
|
1301
|
+
=========================================== */
|
|
1302
|
+
.radio-group {
|
|
1303
|
+
display: flex;
|
|
1304
|
+
flex-direction: column;
|
|
1305
|
+
gap: var(--sw-spacing-sm);
|
|
1306
|
+
margin: var(--sw-spacing-sm) 0 var(--sw-spacing-md) 0;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.radio-option {
|
|
1310
|
+
display: flex;
|
|
1311
|
+
align-items: center;
|
|
1312
|
+
gap: var(--sw-spacing-sm);
|
|
1313
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
1314
|
+
border: 1px solid var(--sw-color-border);
|
|
1315
|
+
border-radius: var(--sw-radius-md);
|
|
1316
|
+
cursor: pointer;
|
|
1317
|
+
transition: background-color var(--sw-transition), border-color var(--sw-transition);
|
|
1318
|
+
margin: 0;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.radio-option:hover {
|
|
1322
|
+
background-color: var(--sw-color-bg);
|
|
1323
|
+
border-color: var(--sw-color-text-light);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.radio-option input[type="radio"] {
|
|
1327
|
+
margin: 0;
|
|
1328
|
+
cursor: pointer;
|
|
1329
|
+
accent-color: var(--sw-color-primary);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.radio-option input[type="radio"]:checked + span {
|
|
1333
|
+
font-weight: 500;
|
|
1334
|
+
color: var(--sw-color-primary);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/* ===========================================
|
|
1338
|
+
Checkbox Group
|
|
1339
|
+
=========================================== */
|
|
1340
|
+
.checkbox-group {
|
|
1341
|
+
display: flex;
|
|
1342
|
+
flex-direction: column;
|
|
1343
|
+
gap: var(--sw-spacing-sm);
|
|
1344
|
+
margin: var(--sw-spacing-sm) 0 var(--sw-spacing-md) 0;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.checkbox-group-actions {
|
|
1348
|
+
display: flex;
|
|
1349
|
+
gap: var(--sw-spacing-sm);
|
|
1350
|
+
margin-bottom: var(--sw-spacing-sm);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.checkbox-group-actions .btn-sm {
|
|
1354
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
1355
|
+
font-size: var(--sw-font-size-sm);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
.checkbox-item {
|
|
1359
|
+
display: flex;
|
|
1360
|
+
align-items: flex-start;
|
|
1361
|
+
gap: var(--sw-spacing-sm);
|
|
1362
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
1363
|
+
border: 1px solid var(--sw-color-border);
|
|
1364
|
+
border-radius: var(--sw-radius-md);
|
|
1365
|
+
cursor: pointer;
|
|
1366
|
+
transition: background-color var(--sw-transition), border-color var(--sw-transition);
|
|
1367
|
+
margin: 0;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.checkbox-item:hover {
|
|
1371
|
+
background-color: var(--sw-color-bg);
|
|
1372
|
+
border-color: var(--sw-color-text-light);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
.checkbox-item input[type="checkbox"] {
|
|
1376
|
+
margin: 0;
|
|
1377
|
+
margin-top: 2px;
|
|
1378
|
+
cursor: pointer;
|
|
1379
|
+
accent-color: var(--sw-color-primary);
|
|
1380
|
+
flex-shrink: 0;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.checkbox-item input[type="checkbox"]:checked ~ p {
|
|
1384
|
+
color: var(--sw-color-primary);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/* ===========================================
|
|
1388
|
+
Card - Clean, Elevated
|
|
1389
|
+
=========================================== */
|
|
1390
|
+
.card {
|
|
1391
|
+
background: var(--card);
|
|
1392
|
+
color: var(--card-foreground);
|
|
1393
|
+
border: 1px solid var(--border);
|
|
1394
|
+
border-left: var(--sw-card-border-left);
|
|
1395
|
+
border-radius: calc(var(--radius) + 4px);
|
|
1396
|
+
padding: var(--sw-spacing-lg);
|
|
1397
|
+
margin-bottom: var(--sw-spacing-md);
|
|
1398
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
1399
|
+
overflow: hidden;
|
|
1400
|
+
word-wrap: break-word;
|
|
1401
|
+
overflow-wrap: break-word;
|
|
1402
|
+
max-width: 100%;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/* Cards using the header/body sub-component pattern manage their
|
|
1406
|
+
own padding so the header's shaded band can reach the card's
|
|
1407
|
+
edges flush against its rounded corners. */
|
|
1408
|
+
.card:has(> .card-header) {
|
|
1409
|
+
padding: 0;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
.card h3 {
|
|
1413
|
+
margin-top: 0;
|
|
1414
|
+
margin-bottom: var(--sw-spacing-sm);
|
|
1415
|
+
color: var(--card-foreground);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/* Card sub-components */
|
|
1419
|
+
.card-header {
|
|
1420
|
+
padding: 1.5rem 1.5rem 0;
|
|
1421
|
+
margin-bottom: 0;
|
|
1422
|
+
border-bottom: none;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.card-header h1, .card-header h2, .card-header h3,
|
|
1426
|
+
.card-header h4, .card-header h5, .card-header h6 {
|
|
1427
|
+
margin: 0;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
.card-header--badged {
|
|
1431
|
+
display: flex;
|
|
1432
|
+
align-items: center;
|
|
1433
|
+
gap: 12px;
|
|
1434
|
+
padding: 14px 18px;
|
|
1435
|
+
background: var(--sw-surface-elevated, #EDECE6);
|
|
1436
|
+
border-bottom: 1px solid var(--sw-border, #E0DED6);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.card-header__badge {
|
|
1440
|
+
font-family: var(--sw-font-mono, 'SFMono-Regular', 'Cascadia Code', monospace);
|
|
1441
|
+
font-size: 11px;
|
|
1442
|
+
font-weight: 600;
|
|
1443
|
+
color: var(--sw-accent);
|
|
1444
|
+
background: color-mix(in oklch, var(--sw-accent) 12%, transparent);
|
|
1445
|
+
padding: 2px 7px;
|
|
1446
|
+
border-radius: 3px;
|
|
1447
|
+
flex-shrink: 0;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
.card-header__title {
|
|
1451
|
+
font-weight: 600;
|
|
1452
|
+
font-size: .95rem;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.card-header__meta {
|
|
1456
|
+
font-size: .8rem;
|
|
1457
|
+
color: var(--sw-text-dim);
|
|
1458
|
+
margin-left: auto;
|
|
1459
|
+
white-space: nowrap;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
.card-body {
|
|
1463
|
+
padding: 1.5rem;
|
|
1464
|
+
padding-top: 0;
|
|
1465
|
+
overflow-x: auto;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.card-body > *:first-child {
|
|
1469
|
+
margin-top: 0;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.card-body > *:last-child {
|
|
1473
|
+
margin-bottom: 0;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
.card-footer {
|
|
1477
|
+
padding: 0 1.5rem 1.5rem;
|
|
1478
|
+
margin-top: 0;
|
|
1479
|
+
border-top: 1px solid var(--border);
|
|
1480
|
+
display: flex;
|
|
1481
|
+
justify-content: flex-end;
|
|
1482
|
+
gap: var(--sw-spacing-sm);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.card-footer button {
|
|
1486
|
+
margin: 0;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/* ===========================================
|
|
1490
|
+
Stack Components (VStack / HStack)
|
|
1491
|
+
=========================================== */
|
|
1492
|
+
.sw-vstack {
|
|
1493
|
+
display: flex;
|
|
1494
|
+
flex-direction: column;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
.sw-hstack {
|
|
1498
|
+
display: flex;
|
|
1499
|
+
flex-direction: row;
|
|
1500
|
+
flex-wrap: wrap;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/* Alignment classes */
|
|
1504
|
+
.sw-align-start { align-items: flex-start; }
|
|
1505
|
+
.sw-align-center { align-items: center; }
|
|
1506
|
+
.sw-align-end { align-items: flex-end; }
|
|
1507
|
+
.sw-align-stretch { align-items: stretch; }
|
|
1508
|
+
|
|
1509
|
+
/* Justify classes (for hstack mainly) */
|
|
1510
|
+
.sw-justify-start { justify-content: flex-start; }
|
|
1511
|
+
.sw-justify-center { justify-content: center; }
|
|
1512
|
+
.sw-justify-end { justify-content: flex-end; }
|
|
1513
|
+
.sw-justify-between { justify-content: space-between; }
|
|
1514
|
+
|
|
1515
|
+
/* Divider support for stacks */
|
|
1516
|
+
.sw-vstack.sw-divider > *:not(:last-child) {
|
|
1517
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
1518
|
+
padding-bottom: inherit;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.sw-hstack.sw-divider > *:not(:last-child) {
|
|
1522
|
+
border-right: 1px solid var(--sw-color-border);
|
|
1523
|
+
padding-right: inherit;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/* ===========================================
|
|
1527
|
+
Grid Component
|
|
1528
|
+
=========================================== */
|
|
1529
|
+
.sw-grid {
|
|
1530
|
+
display: grid;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/* Responsive grid - mobile first */
|
|
1534
|
+
@media (max-width: 639px) {
|
|
1535
|
+
.sw-grid[data-cols-sm] { grid-template-columns: repeat(var(--sw-grid-cols-sm, 1), 1fr); }
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
@media (min-width: 640px) and (max-width: 1023px) {
|
|
1539
|
+
.sw-grid[data-cols-md] { grid-template-columns: repeat(var(--sw-grid-cols-md, 2), 1fr); }
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
@media (min-width: 1024px) {
|
|
1543
|
+
.sw-grid[data-cols-lg] { grid-template-columns: repeat(var(--sw-grid-cols-lg, 3), 1fr); }
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/* ===========================================
|
|
1547
|
+
Tabs Component
|
|
1548
|
+
=========================================== */
|
|
1549
|
+
.sw-tabs {
|
|
1550
|
+
margin: var(--sw-spacing-md) 0;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.sw-tabs-list {
|
|
1554
|
+
display: flex;
|
|
1555
|
+
gap: var(--sw-spacing-xs);
|
|
1556
|
+
border-bottom: 2px solid var(--border);
|
|
1557
|
+
margin-bottom: var(--sw-spacing-md);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.sw-tab-trigger {
|
|
1561
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
1562
|
+
border: none;
|
|
1563
|
+
background: transparent;
|
|
1564
|
+
font-family: var(--sw-font-body);
|
|
1565
|
+
font-size: var(--sw-font-size-base);
|
|
1566
|
+
font-weight: 500;
|
|
1567
|
+
color: var(--muted-foreground);
|
|
1568
|
+
cursor: pointer;
|
|
1569
|
+
/* Only transition color on hover, not border/active state - prevents flash during HTMX swaps */
|
|
1570
|
+
transition: color var(--sw-transition);
|
|
1571
|
+
position: relative;
|
|
1572
|
+
margin: 0;
|
|
1573
|
+
margin-bottom: -2px;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.sw-tab-trigger:hover {
|
|
1577
|
+
color: var(--foreground);
|
|
1578
|
+
transform: none;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
.sw-tab-trigger.sw-tab-active {
|
|
1582
|
+
color: var(--foreground);
|
|
1583
|
+
background: var(--background);
|
|
1584
|
+
border-bottom: 2px solid var(--foreground);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.sw-tab-panel {
|
|
1588
|
+
padding: var(--sw-spacing-sm) 0;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/* Tabs Variants */
|
|
1592
|
+
.sw-tabs-enclosed .sw-tabs-list {
|
|
1593
|
+
border-bottom: none;
|
|
1594
|
+
gap: 0;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.sw-tabs-enclosed .sw-tab-trigger {
|
|
1598
|
+
border: 1px solid transparent;
|
|
1599
|
+
border-bottom: none;
|
|
1600
|
+
border-radius: var(--sw-radius-md) var(--sw-radius-md) 0 0;
|
|
1601
|
+
background: var(--sw-color-bg-elevated);
|
|
1602
|
+
margin-bottom: -1px;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
.sw-tabs-enclosed .sw-tab-trigger.sw-tab-active {
|
|
1606
|
+
background: var(--sw-color-bg-card);
|
|
1607
|
+
border-color: var(--sw-color-border);
|
|
1608
|
+
border-bottom-color: var(--sw-color-bg-card);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.sw-tabs-enclosed .sw-tab-panel {
|
|
1612
|
+
border: 1px solid var(--sw-color-border);
|
|
1613
|
+
border-radius: 0 var(--sw-radius-md) var(--sw-radius-md) var(--sw-radius-md);
|
|
1614
|
+
padding: var(--sw-spacing-md);
|
|
1615
|
+
background: var(--sw-color-bg-card);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
.sw-tabs-soft-rounded .sw-tabs-list {
|
|
1619
|
+
border-bottom: none;
|
|
1620
|
+
background: var(--muted);
|
|
1621
|
+
padding: var(--sw-spacing-xs);
|
|
1622
|
+
border-radius: var(--sw-radius-lg);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
.sw-tabs-soft-rounded .sw-tab-trigger {
|
|
1626
|
+
border-radius: var(--radius);
|
|
1627
|
+
margin-bottom: 0;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.sw-tabs-soft-rounded .sw-tab-trigger.sw-tab-active {
|
|
1631
|
+
background: var(--background);
|
|
1632
|
+
box-shadow: var(--sw-shadow-sm);
|
|
1633
|
+
border-bottom: none;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/* ===========================================
|
|
1637
|
+
Breadcrumbs Component
|
|
1638
|
+
=========================================== */
|
|
1639
|
+
.sw-breadcrumbs {
|
|
1640
|
+
margin: var(--sw-spacing-sm) 0;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.sw-breadcrumbs-list {
|
|
1644
|
+
display: flex;
|
|
1645
|
+
align-items: center;
|
|
1646
|
+
flex-wrap: wrap;
|
|
1647
|
+
list-style: none;
|
|
1648
|
+
padding: 0;
|
|
1649
|
+
margin: 0;
|
|
1650
|
+
gap: var(--sw-spacing-xs);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
.sw-breadcrumb-item {
|
|
1654
|
+
display: flex;
|
|
1655
|
+
align-items: center;
|
|
1656
|
+
gap: var(--sw-spacing-xs);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
.sw-breadcrumb-separator {
|
|
1660
|
+
color: var(--sw-color-text-light);
|
|
1661
|
+
font-size: var(--sw-font-size-sm);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
.sw-breadcrumb-link {
|
|
1665
|
+
color: var(--sw-color-primary);
|
|
1666
|
+
text-decoration: none;
|
|
1667
|
+
font-size: var(--sw-font-size-sm);
|
|
1668
|
+
transition: color var(--sw-transition);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
.sw-breadcrumb-link:hover {
|
|
1672
|
+
color: var(--sw-color-primary-hover);
|
|
1673
|
+
text-decoration: underline;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.sw-breadcrumb-current {
|
|
1677
|
+
color: var(--sw-color-text-muted);
|
|
1678
|
+
font-size: var(--sw-font-size-sm);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
/* ===========================================
|
|
1682
|
+
Navbar Component
|
|
1683
|
+
=========================================== */
|
|
1684
|
+
.sw-navbar {
|
|
1685
|
+
display: flex;
|
|
1686
|
+
align-items: center;
|
|
1687
|
+
gap: var(--sw-spacing-xs);
|
|
1688
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-md);
|
|
1689
|
+
background: var(--sw-color-bg-elevated);
|
|
1690
|
+
border-bottom: 1px solid var(--border);
|
|
1691
|
+
margin-bottom: var(--sw-spacing-md);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
.sw-navbar-item {
|
|
1695
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
1696
|
+
color: var(--sw-color-text-muted);
|
|
1697
|
+
text-decoration: none;
|
|
1698
|
+
border-radius: var(--sw-radius-sm);
|
|
1699
|
+
font-size: var(--sw-font-size-sm);
|
|
1700
|
+
transition: color var(--sw-transition), background var(--sw-transition);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.sw-navbar-item:hover {
|
|
1704
|
+
color: var(--sw-color-text);
|
|
1705
|
+
background: var(--muted);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
.sw-navbar-item-active {
|
|
1709
|
+
font-weight: 600;
|
|
1710
|
+
color: var(--sw-color-text);
|
|
1711
|
+
cursor: default;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.sw-navbar-item__close {
|
|
1715
|
+
margin-left: 0.35em;
|
|
1716
|
+
opacity: 0.65;
|
|
1717
|
+
font-weight: 400;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/* ===========================================
|
|
1721
|
+
Dropdown/Menu Component
|
|
1722
|
+
=========================================== */
|
|
1723
|
+
.sw-dropdown {
|
|
1724
|
+
position: relative;
|
|
1725
|
+
display: inline-block;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.sw-dropdown-trigger {
|
|
1729
|
+
cursor: pointer;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.sw-dropdown-menu {
|
|
1733
|
+
position: absolute;
|
|
1734
|
+
top: 100%;
|
|
1735
|
+
left: 0;
|
|
1736
|
+
z-index: 100;
|
|
1737
|
+
min-width: 180px;
|
|
1738
|
+
margin-top: var(--sw-spacing-xs);
|
|
1739
|
+
padding: var(--sw-spacing-xs) 0;
|
|
1740
|
+
background: var(--popover);
|
|
1741
|
+
color: var(--popover-foreground);
|
|
1742
|
+
border: 1px solid var(--border);
|
|
1743
|
+
border-radius: var(--radius);
|
|
1744
|
+
box-shadow: var(--sw-shadow-lg);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
.sw-menu-item {
|
|
1748
|
+
display: block;
|
|
1749
|
+
width: 100%;
|
|
1750
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
1751
|
+
border: none;
|
|
1752
|
+
background: transparent;
|
|
1753
|
+
font-family: var(--sw-font-body);
|
|
1754
|
+
font-size: var(--sw-font-size-sm);
|
|
1755
|
+
font-weight: 400;
|
|
1756
|
+
color: var(--sw-color-text);
|
|
1757
|
+
text-align: left;
|
|
1758
|
+
cursor: pointer;
|
|
1759
|
+
transition: background var(--sw-transition);
|
|
1760
|
+
margin: 0;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
.sw-menu-item:hover {
|
|
1764
|
+
background: var(--accent);
|
|
1765
|
+
color: var(--accent-foreground);
|
|
1766
|
+
transform: none;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
.sw-menu-item-destructive {
|
|
1770
|
+
color: var(--destructive);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
.sw-menu-item-destructive:hover {
|
|
1774
|
+
background: color-mix(in oklch, var(--destructive) 10%, transparent);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
.sw-menu-divider {
|
|
1778
|
+
margin: var(--sw-spacing-xs) 0;
|
|
1779
|
+
border: none;
|
|
1780
|
+
border-top: 1px solid var(--sw-color-border);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/* Dropdown transitions */
|
|
1784
|
+
.sw-transition-enter { transition: all var(--sw-transition); }
|
|
1785
|
+
.sw-transition-enter-start { opacity: 0; transform: translateY(-4px); }
|
|
1786
|
+
.sw-transition-enter-end { opacity: 1; transform: translateY(0); }
|
|
1787
|
+
.sw-transition-leave { transition: all var(--sw-transition-fast); }
|
|
1788
|
+
.sw-transition-leave-start { opacity: 1; transform: translateY(0); }
|
|
1789
|
+
.sw-transition-leave-end { opacity: 0; transform: translateY(-4px); }
|
|
1790
|
+
|
|
1791
|
+
/* ===========================================
|
|
1792
|
+
Modal Component
|
|
1793
|
+
=========================================== */
|
|
1794
|
+
.sw-modal-wrapper {
|
|
1795
|
+
position: relative;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
.sw-modal-backdrop {
|
|
1799
|
+
position: fixed;
|
|
1800
|
+
inset: 0;
|
|
1801
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1802
|
+
z-index: 999;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
.sw-modal {
|
|
1806
|
+
position: fixed;
|
|
1807
|
+
top: 50%;
|
|
1808
|
+
left: 50%;
|
|
1809
|
+
transform: translate(-50%, -50%);
|
|
1810
|
+
z-index: 1000;
|
|
1811
|
+
background: var(--popover);
|
|
1812
|
+
color: var(--popover-foreground);
|
|
1813
|
+
border: 1px solid var(--border);
|
|
1814
|
+
border-radius: var(--sw-radius-lg);
|
|
1815
|
+
box-shadow: var(--sw-shadow-xl);
|
|
1816
|
+
max-height: 90vh;
|
|
1817
|
+
overflow: hidden;
|
|
1818
|
+
display: flex;
|
|
1819
|
+
flex-direction: column;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* Modal sizes */
|
|
1823
|
+
.sw-modal-sm { width: min(400px, 90vw); }
|
|
1824
|
+
.sw-modal-md { width: min(560px, 90vw); }
|
|
1825
|
+
.sw-modal-lg { width: min(800px, 90vw); }
|
|
1826
|
+
.sw-modal-xl { width: min(1140px, 95vw); }
|
|
1827
|
+
|
|
1828
|
+
.sw-modal-header {
|
|
1829
|
+
display: flex;
|
|
1830
|
+
align-items: center;
|
|
1831
|
+
justify-content: space-between;
|
|
1832
|
+
padding: var(--sw-spacing-md) var(--sw-spacing-lg);
|
|
1833
|
+
border-bottom: 1px solid var(--border);
|
|
1834
|
+
flex-shrink: 0;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.sw-modal-title {
|
|
1838
|
+
margin: 0;
|
|
1839
|
+
font-size: 1.25rem;
|
|
1840
|
+
font-weight: 600;
|
|
1841
|
+
color: var(--sw-color-text);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
.sw-modal-close {
|
|
1845
|
+
background: transparent;
|
|
1846
|
+
border: none;
|
|
1847
|
+
font-size: 1.5rem;
|
|
1848
|
+
line-height: 1;
|
|
1849
|
+
color: var(--sw-color-text-muted);
|
|
1850
|
+
cursor: pointer;
|
|
1851
|
+
padding: 0;
|
|
1852
|
+
margin: 0;
|
|
1853
|
+
width: 32px;
|
|
1854
|
+
height: 32px;
|
|
1855
|
+
display: flex;
|
|
1856
|
+
align-items: center;
|
|
1857
|
+
justify-content: center;
|
|
1858
|
+
border-radius: var(--sw-radius-sm);
|
|
1859
|
+
transition: background var(--sw-transition), color var(--sw-transition);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.sw-modal-close:hover {
|
|
1863
|
+
background: var(--sw-color-bg-elevated);
|
|
1864
|
+
color: var(--sw-color-text);
|
|
1865
|
+
transform: none;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
.sw-modal-close-only {
|
|
1869
|
+
position: absolute;
|
|
1870
|
+
top: var(--sw-spacing-sm);
|
|
1871
|
+
right: var(--sw-spacing-sm);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
.sw-modal-body {
|
|
1875
|
+
padding: var(--sw-spacing-lg);
|
|
1876
|
+
overflow-y: auto;
|
|
1877
|
+
flex: 1;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
.sw-modal-body > *:first-child {
|
|
1881
|
+
margin-top: 0;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
.sw-modal-body > *:last-child {
|
|
1885
|
+
margin-bottom: 0;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.sw-modal-footer {
|
|
1889
|
+
display: flex;
|
|
1890
|
+
justify-content: flex-end;
|
|
1891
|
+
gap: var(--sw-spacing-sm);
|
|
1892
|
+
padding: var(--sw-spacing-md) var(--sw-spacing-lg);
|
|
1893
|
+
border-top: 1px solid var(--border);
|
|
1894
|
+
background: var(--muted);
|
|
1895
|
+
flex-shrink: 0;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
.sw-modal-footer button {
|
|
1899
|
+
margin: 0;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/* Modal transitions */
|
|
1903
|
+
.sw-transition-fade-enter { transition: opacity var(--sw-transition); }
|
|
1904
|
+
.sw-transition-fade-enter-start { opacity: 0; }
|
|
1905
|
+
.sw-transition-fade-enter-end { opacity: 1; }
|
|
1906
|
+
.sw-transition-fade-leave { transition: opacity var(--sw-transition-fast); }
|
|
1907
|
+
.sw-transition-fade-leave-start { opacity: 1; }
|
|
1908
|
+
.sw-transition-fade-leave-end { opacity: 0; }
|
|
1909
|
+
|
|
1910
|
+
.sw-transition-modal-enter { transition: all var(--sw-transition); }
|
|
1911
|
+
.sw-transition-modal-enter-start { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
|
|
1912
|
+
.sw-transition-modal-enter-end { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1913
|
+
.sw-transition-modal-leave { transition: all var(--sw-transition-fast); }
|
|
1914
|
+
.sw-transition-modal-leave-start { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1915
|
+
.sw-transition-modal-leave-end { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
|
|
1916
|
+
|
|
1917
|
+
/* ===========================================
|
|
1918
|
+
Alert Component
|
|
1919
|
+
=========================================== */
|
|
1920
|
+
.sw-alert {
|
|
1921
|
+
display: flex;
|
|
1922
|
+
align-items: flex-start;
|
|
1923
|
+
gap: var(--sw-spacing-sm);
|
|
1924
|
+
padding: var(--sw-spacing-md);
|
|
1925
|
+
margin: var(--sw-spacing-md) 0;
|
|
1926
|
+
border-radius: var(--sw-radius-md);
|
|
1927
|
+
border: 1px solid;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
.sw-alert-icon {
|
|
1931
|
+
flex-shrink: 0;
|
|
1932
|
+
font-size: 1.25rem;
|
|
1933
|
+
line-height: 1;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
.sw-alert-content {
|
|
1937
|
+
flex: 1;
|
|
1938
|
+
min-width: 0;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
.sw-alert-title {
|
|
1942
|
+
display: block;
|
|
1943
|
+
margin-bottom: var(--sw-spacing-xs);
|
|
1944
|
+
font-weight: 600;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
.sw-alert-dismiss {
|
|
1948
|
+
flex-shrink: 0;
|
|
1949
|
+
background: transparent;
|
|
1950
|
+
border: none;
|
|
1951
|
+
font-size: 1.25rem;
|
|
1952
|
+
line-height: 1;
|
|
1953
|
+
cursor: pointer;
|
|
1954
|
+
opacity: 0.6;
|
|
1955
|
+
padding: 0;
|
|
1956
|
+
margin: 0;
|
|
1957
|
+
width: 24px;
|
|
1958
|
+
height: 24px;
|
|
1959
|
+
display: flex;
|
|
1960
|
+
align-items: center;
|
|
1961
|
+
justify-content: center;
|
|
1962
|
+
border-radius: var(--sw-radius-sm);
|
|
1963
|
+
transition: opacity var(--sw-transition), background var(--sw-transition);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
.sw-alert-dismiss:hover {
|
|
1967
|
+
opacity: 1;
|
|
1968
|
+
background: rgba(0, 0, 0, 0.1);
|
|
1969
|
+
transform: none;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/* Alert variants */
|
|
1973
|
+
.sw-alert-info {
|
|
1974
|
+
background: color-mix(in oklch, var(--info) 10%, transparent);
|
|
1975
|
+
border-color: color-mix(in oklch, var(--info) 30%, transparent);
|
|
1976
|
+
color: var(--info);
|
|
1977
|
+
}
|
|
1978
|
+
.sw-alert-info .sw-alert-icon { color: var(--info); }
|
|
1979
|
+
|
|
1980
|
+
.sw-alert-success {
|
|
1981
|
+
background: color-mix(in oklch, var(--success) 10%, transparent);
|
|
1982
|
+
border-color: color-mix(in oklch, var(--success) 30%, transparent);
|
|
1983
|
+
color: var(--success);
|
|
1984
|
+
}
|
|
1985
|
+
.sw-alert-success .sw-alert-icon { color: var(--success); }
|
|
1986
|
+
|
|
1987
|
+
.sw-alert-warning {
|
|
1988
|
+
background: color-mix(in oklch, var(--warning) 10%, transparent);
|
|
1989
|
+
border-color: color-mix(in oklch, var(--warning) 30%, transparent);
|
|
1990
|
+
color: var(--warning);
|
|
1991
|
+
}
|
|
1992
|
+
.sw-alert-warning .sw-alert-icon { color: var(--warning); }
|
|
1993
|
+
|
|
1994
|
+
.sw-alert-error {
|
|
1995
|
+
background: color-mix(in oklch, var(--destructive) 10%, transparent);
|
|
1996
|
+
border-color: color-mix(in oklch, var(--destructive) 30%, transparent);
|
|
1997
|
+
color: var(--destructive);
|
|
1998
|
+
}
|
|
1999
|
+
.sw-alert-error .sw-alert-icon { color: var(--destructive); }
|
|
2000
|
+
|
|
2001
|
+
/* ===========================================
|
|
2002
|
+
Toast Component (Multi-toast Stack)
|
|
2003
|
+
=========================================== */
|
|
2004
|
+
.sw-toast-container {
|
|
2005
|
+
position: fixed;
|
|
2006
|
+
z-index: 1100;
|
|
2007
|
+
pointer-events: none;
|
|
2008
|
+
display: flex;
|
|
2009
|
+
flex-direction: column;
|
|
2010
|
+
gap: var(--sw-spacing-sm);
|
|
2011
|
+
max-height: 100vh;
|
|
2012
|
+
overflow: hidden;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
.sw-toast-container > * {
|
|
2016
|
+
pointer-events: auto;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.sw-toast-top-right { top: var(--sw-spacing-lg); right: var(--sw-spacing-lg); }
|
|
2020
|
+
.sw-toast-top-left { top: var(--sw-spacing-lg); left: var(--sw-spacing-lg); }
|
|
2021
|
+
.sw-toast-bottom-right { bottom: var(--sw-spacing-lg); right: var(--sw-spacing-lg); flex-direction: column-reverse; }
|
|
2022
|
+
.sw-toast-bottom-left { bottom: var(--sw-spacing-lg); left: var(--sw-spacing-lg); flex-direction: column-reverse; }
|
|
2023
|
+
|
|
2024
|
+
.sw-toast {
|
|
2025
|
+
display: flex;
|
|
2026
|
+
align-items: center;
|
|
2027
|
+
gap: var(--sw-spacing-sm);
|
|
2028
|
+
min-width: 280px;
|
|
2029
|
+
max-width: 420px;
|
|
2030
|
+
padding: var(--sw-spacing-md);
|
|
2031
|
+
background: var(--card);
|
|
2032
|
+
color: var(--card-foreground);
|
|
2033
|
+
border-radius: var(--radius);
|
|
2034
|
+
box-shadow: var(--sw-shadow-xl);
|
|
2035
|
+
border: 1px solid var(--border);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
.sw-toast-icon {
|
|
2039
|
+
flex-shrink: 0;
|
|
2040
|
+
font-size: 1.25rem;
|
|
2041
|
+
line-height: 1;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.sw-toast-message {
|
|
2045
|
+
flex: 1;
|
|
2046
|
+
min-width: 0;
|
|
2047
|
+
font-size: var(--sw-font-size-sm);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.sw-toast-dismiss {
|
|
2051
|
+
flex-shrink: 0;
|
|
2052
|
+
background: transparent;
|
|
2053
|
+
border: none;
|
|
2054
|
+
font-size: 1rem;
|
|
2055
|
+
line-height: 1;
|
|
2056
|
+
cursor: pointer;
|
|
2057
|
+
opacity: 0.5;
|
|
2058
|
+
padding: 0;
|
|
2059
|
+
margin: 0;
|
|
2060
|
+
width: 20px;
|
|
2061
|
+
height: 20px;
|
|
2062
|
+
display: flex;
|
|
2063
|
+
align-items: center;
|
|
2064
|
+
justify-content: center;
|
|
2065
|
+
border-radius: var(--sw-radius-sm);
|
|
2066
|
+
transition: opacity var(--sw-transition);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
.sw-toast-dismiss:hover {
|
|
2070
|
+
opacity: 1;
|
|
2071
|
+
transform: none;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
/* Toast variants */
|
|
2075
|
+
.sw-toast-info .sw-toast-icon { color: var(--info); }
|
|
2076
|
+
.sw-toast-success .sw-toast-icon { color: var(--success); }
|
|
2077
|
+
.sw-toast-warning .sw-toast-icon { color: var(--warning); }
|
|
2078
|
+
.sw-toast-error .sw-toast-icon { color: var(--destructive); }
|
|
2079
|
+
|
|
2080
|
+
/* Toast transitions */
|
|
2081
|
+
.sw-transition-toast-enter { transition: all var(--sw-transition); }
|
|
2082
|
+
.sw-transition-toast-enter-start { opacity: 0; transform: translateX(100%); }
|
|
2083
|
+
.sw-transition-toast-enter-end { opacity: 1; transform: translateX(0); }
|
|
2084
|
+
.sw-transition-toast-leave { transition: all var(--sw-transition); }
|
|
2085
|
+
.sw-transition-toast-leave-start { opacity: 1; transform: translateX(0); }
|
|
2086
|
+
.sw-transition-toast-leave-end { opacity: 0; transform: translateX(100%); }
|
|
2087
|
+
|
|
2088
|
+
/* ===========================================
|
|
2089
|
+
Progress Bar Component
|
|
2090
|
+
=========================================== */
|
|
2091
|
+
.sw-progress {
|
|
2092
|
+
position: relative;
|
|
2093
|
+
height: 8px;
|
|
2094
|
+
background: var(--muted);
|
|
2095
|
+
border-radius: var(--sw-radius-sm);
|
|
2096
|
+
overflow: hidden;
|
|
2097
|
+
margin: var(--sw-spacing-sm) 0;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
.sw-progress-bar {
|
|
2101
|
+
height: 100%;
|
|
2102
|
+
background: var(--primary);
|
|
2103
|
+
border-radius: var(--sw-radius-sm);
|
|
2104
|
+
transition: width 0.3s ease;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.sw-progress-label {
|
|
2108
|
+
position: absolute;
|
|
2109
|
+
right: var(--sw-spacing-xs);
|
|
2110
|
+
top: 50%;
|
|
2111
|
+
transform: translateY(-50%);
|
|
2112
|
+
font-size: 10px;
|
|
2113
|
+
font-weight: 600;
|
|
2114
|
+
color: var(--sw-color-text);
|
|
2115
|
+
text-shadow: 0 0 2px var(--sw-color-bg-card);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
/* Progress variants */
|
|
2119
|
+
.sw-progress-success .sw-progress-bar { background: var(--success); }
|
|
2120
|
+
.sw-progress-warning .sw-progress-bar { background: var(--warning); }
|
|
2121
|
+
.sw-progress-error .sw-progress-bar { background: var(--destructive); }
|
|
2122
|
+
|
|
2123
|
+
/* Animated progress */
|
|
2124
|
+
.sw-progress-animated .sw-progress-bar {
|
|
2125
|
+
background-image: linear-gradient(
|
|
2126
|
+
45deg,
|
|
2127
|
+
rgba(255, 255, 255, 0.15) 25%,
|
|
2128
|
+
transparent 25%,
|
|
2129
|
+
transparent 50%,
|
|
2130
|
+
rgba(255, 255, 255, 0.15) 50%,
|
|
2131
|
+
rgba(255, 255, 255, 0.15) 75%,
|
|
2132
|
+
transparent 75%,
|
|
2133
|
+
transparent
|
|
2134
|
+
);
|
|
2135
|
+
background-size: 1rem 1rem;
|
|
2136
|
+
animation: sw-progress-stripes 1s linear infinite;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
@keyframes sw-progress-stripes {
|
|
2140
|
+
0% { background-position: 1rem 0; }
|
|
2141
|
+
100% { background-position: 0 0; }
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
/* ===========================================
|
|
2145
|
+
Spinner Component
|
|
2146
|
+
=========================================== */
|
|
2147
|
+
.sw-spinner-container {
|
|
2148
|
+
display: inline-flex;
|
|
2149
|
+
align-items: center;
|
|
2150
|
+
gap: var(--sw-spacing-sm);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
.sw-spinner {
|
|
2154
|
+
border: 2px solid var(--sw-color-border);
|
|
2155
|
+
border-top-color: var(--sw-color-primary);
|
|
2156
|
+
border-radius: 50%;
|
|
2157
|
+
animation: sw-spin 0.8s linear infinite;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
.sw-spinner-sm { width: 16px; height: 16px; }
|
|
2161
|
+
.sw-spinner-md { width: 24px; height: 24px; }
|
|
2162
|
+
.sw-spinner-lg { width: 40px; height: 40px; border-width: 3px; }
|
|
2163
|
+
|
|
2164
|
+
.sw-spinner-label {
|
|
2165
|
+
font-size: var(--sw-font-size-sm);
|
|
2166
|
+
color: var(--sw-color-text-muted);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
@keyframes sw-spin {
|
|
2170
|
+
to { transform: rotate(360deg); }
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/* ===========================================
|
|
2174
|
+
Theme Switcher Component
|
|
2175
|
+
=========================================== */
|
|
2176
|
+
.sw-theme-switcher {
|
|
2177
|
+
display: inline-flex;
|
|
2178
|
+
align-items: center;
|
|
2179
|
+
gap: var(--sw-spacing-sm);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
.sw-theme-switcher-fixed {
|
|
2183
|
+
position: fixed;
|
|
2184
|
+
top: var(--sw-spacing-md);
|
|
2185
|
+
right: var(--sw-spacing-md);
|
|
2186
|
+
z-index: 1000;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
.sw-theme-switcher-label {
|
|
2190
|
+
font-size: var(--sw-font-size-sm);
|
|
2191
|
+
color: var(--sw-color-text-muted);
|
|
2192
|
+
font-weight: 500;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
.sw-theme-switcher-dropdown {
|
|
2196
|
+
position: relative;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
.sw-theme-switcher-trigger {
|
|
2200
|
+
display: flex;
|
|
2201
|
+
align-items: center;
|
|
2202
|
+
gap: var(--sw-spacing-xs);
|
|
2203
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
2204
|
+
background: var(--sw-color-bg-card);
|
|
2205
|
+
border: 1px solid var(--sw-color-border);
|
|
2206
|
+
border-radius: var(--sw-radius-md);
|
|
2207
|
+
font-size: var(--sw-font-size-sm);
|
|
2208
|
+
color: var(--sw-color-text);
|
|
2209
|
+
cursor: pointer;
|
|
2210
|
+
transition: border-color var(--sw-transition), box-shadow var(--sw-transition);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.sw-theme-switcher-trigger:hover {
|
|
2214
|
+
border-color: var(--sw-color-border-strong);
|
|
2215
|
+
transform: none;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.sw-theme-switcher-trigger:focus {
|
|
2219
|
+
outline: none;
|
|
2220
|
+
border-color: var(--sw-color-primary);
|
|
2221
|
+
box-shadow: 0 0 0 2px var(--sw-color-primary-glow);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
.sw-theme-switcher-arrow {
|
|
2225
|
+
font-size: 10px;
|
|
2226
|
+
color: var(--sw-color-text-muted);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.sw-theme-switcher-menu {
|
|
2230
|
+
position: absolute;
|
|
2231
|
+
top: 100%;
|
|
2232
|
+
right: 0;
|
|
2233
|
+
margin-top: var(--sw-spacing-xs);
|
|
2234
|
+
min-width: 180px;
|
|
2235
|
+
background: var(--sw-color-bg-card);
|
|
2236
|
+
border: 1px solid var(--sw-color-border);
|
|
2237
|
+
border-radius: var(--sw-radius-md);
|
|
2238
|
+
box-shadow: var(--sw-shadow-lg);
|
|
2239
|
+
overflow: hidden;
|
|
2240
|
+
z-index: 1001;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
.sw-theme-switcher-option {
|
|
2244
|
+
display: flex;
|
|
2245
|
+
flex-direction: column;
|
|
2246
|
+
align-items: flex-start;
|
|
2247
|
+
width: 100%;
|
|
2248
|
+
padding: var(--sw-spacing-sm) var(--sw-spacing-md);
|
|
2249
|
+
background: transparent;
|
|
2250
|
+
border: none;
|
|
2251
|
+
text-align: left;
|
|
2252
|
+
cursor: pointer;
|
|
2253
|
+
transition: background var(--sw-transition-fast);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
.sw-theme-switcher-option:hover {
|
|
2257
|
+
background: var(--sw-color-bg-elevated);
|
|
2258
|
+
transform: none;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
.sw-theme-switcher-option-label {
|
|
2262
|
+
font-size: var(--sw-font-size-sm);
|
|
2263
|
+
font-weight: 500;
|
|
2264
|
+
color: var(--sw-color-text);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
.sw-theme-switcher-option-desc {
|
|
2268
|
+
font-size: 12px;
|
|
2269
|
+
color: var(--sw-color-text-muted);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/* ===========================================
|
|
2273
|
+
Lesson Text & Terms (Educational Content)
|
|
2274
|
+
=========================================== */
|
|
2275
|
+
.lesson-text {
|
|
2276
|
+
position: relative;
|
|
2277
|
+
line-height: 1.8;
|
|
2278
|
+
font-size: var(--sw-font-size-lg);
|
|
2279
|
+
color: var(--sw-color-text);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
.sw-term, .term {
|
|
2283
|
+
text-decoration: underline;
|
|
2284
|
+
text-decoration-style: dotted;
|
|
2285
|
+
text-decoration-color: var(--sw-term-color);
|
|
2286
|
+
text-underline-offset: 3px;
|
|
2287
|
+
cursor: help;
|
|
2288
|
+
color: var(--sw-term-color);
|
|
2289
|
+
font-weight: 500;
|
|
2290
|
+
padding: 0 2px;
|
|
2291
|
+
border-radius: var(--sw-radius-sm);
|
|
2292
|
+
transition: background-color var(--sw-transition), color var(--sw-transition);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
.sw-term:hover, .sw-term:focus,
|
|
2296
|
+
.term:hover, .term:focus {
|
|
2297
|
+
background-color: var(--sw-term-bg-hover);
|
|
2298
|
+
outline: none;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/* ===========================================
|
|
2302
|
+
Tooltip
|
|
2303
|
+
=========================================== */
|
|
2304
|
+
.sw-tooltip, .tooltip {
|
|
2305
|
+
position: fixed;
|
|
2306
|
+
transform: translateX(-50%) translateY(-100%);
|
|
2307
|
+
background: var(--sw-tooltip-bg);
|
|
2308
|
+
color: var(--sw-tooltip-text);
|
|
2309
|
+
padding: var(--sw-spacing-md);
|
|
2310
|
+
border-radius: var(--sw-radius-md);
|
|
2311
|
+
font-size: var(--sw-font-size-sm);
|
|
2312
|
+
max-width: 350px;
|
|
2313
|
+
box-shadow: var(--sw-shadow-lg);
|
|
2314
|
+
z-index: 1000;
|
|
2315
|
+
cursor: pointer;
|
|
2316
|
+
white-space: normal;
|
|
2317
|
+
word-wrap: break-word;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
.sw-tooltip::after, .tooltip::after {
|
|
2321
|
+
content: '';
|
|
2322
|
+
position: absolute;
|
|
2323
|
+
top: 100%;
|
|
2324
|
+
left: 50%;
|
|
2325
|
+
transform: translateX(-50%);
|
|
2326
|
+
border: 8px solid transparent;
|
|
2327
|
+
border-top-color: var(--sw-tooltip-bg);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
.tooltip-content {
|
|
2331
|
+
line-height: 1.5;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
.tooltip-hint {
|
|
2335
|
+
font-size: 12px;
|
|
2336
|
+
color: #94a3b8;
|
|
2337
|
+
margin-top: var(--sw-spacing-sm);
|
|
2338
|
+
font-style: italic;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
/* ===========================================
|
|
2342
|
+
Collapsible - Refined Accordion
|
|
2343
|
+
=========================================== */
|
|
2344
|
+
.collapsible {
|
|
2345
|
+
margin: var(--sw-spacing-md) 0;
|
|
2346
|
+
border: 1px solid var(--sw-color-border);
|
|
2347
|
+
border-radius: var(--sw-radius-lg);
|
|
2348
|
+
overflow: hidden;
|
|
2349
|
+
transition: box-shadow var(--sw-transition);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
.collapsible:hover {
|
|
2353
|
+
box-shadow: var(--sw-shadow-sm);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
.collapsible-header {
|
|
2357
|
+
display: flex;
|
|
2358
|
+
align-items: center;
|
|
2359
|
+
gap: var(--sw-spacing-sm);
|
|
2360
|
+
padding: var(--sw-spacing-md) var(--sw-spacing-lg);
|
|
2361
|
+
background: var(--sw-color-bg-elevated);
|
|
2362
|
+
cursor: pointer;
|
|
2363
|
+
transition: background var(--sw-transition);
|
|
2364
|
+
user-select: none;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.collapsible-header:hover {
|
|
2368
|
+
background: var(--sw-color-border);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
.collapsible-icon {
|
|
2372
|
+
font-size: 10px;
|
|
2373
|
+
color: var(--sw-color-primary);
|
|
2374
|
+
width: 16px;
|
|
2375
|
+
height: 16px;
|
|
2376
|
+
display: flex;
|
|
2377
|
+
align-items: center;
|
|
2378
|
+
justify-content: center;
|
|
2379
|
+
background: var(--sw-color-primary-light);
|
|
2380
|
+
border-radius: var(--sw-radius-sm);
|
|
2381
|
+
transition: transform var(--sw-transition);
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
.collapsible-label {
|
|
2385
|
+
font-family: var(--sw-font-body);
|
|
2386
|
+
font-weight: 600;
|
|
2387
|
+
font-size: var(--sw-font-size-sm);
|
|
2388
|
+
color: var(--sw-color-text);
|
|
2389
|
+
letter-spacing: 0.01em;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.collapsible-content {
|
|
2393
|
+
padding: var(--sw-spacing-lg);
|
|
2394
|
+
border-top: 1px solid var(--sw-color-border);
|
|
2395
|
+
line-height: var(--sw-line-height);
|
|
2396
|
+
background: var(--sw-color-bg-card);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
.sw-collapsible-subtitle {
|
|
2400
|
+
font-size: 0.8rem;
|
|
2401
|
+
color: var(--sw-color-text-muted, #666);
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
.sw-collapsible-badge {
|
|
2405
|
+
margin-left: auto;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
/* ===========================================
|
|
2409
|
+
Scroll Box
|
|
2410
|
+
=========================================== */
|
|
2411
|
+
.sw-scroll-box {
|
|
2412
|
+
position: relative;
|
|
2413
|
+
border: 1px solid var(--sw-color-border);
|
|
2414
|
+
border-radius: var(--sw-radius-md);
|
|
2415
|
+
padding: var(--sw-spacing-md);
|
|
2416
|
+
background: var(--sw-color-bg-card);
|
|
2417
|
+
transition: box-shadow var(--sw-transition);
|
|
2418
|
+
scrollbar-width: thin;
|
|
2419
|
+
scrollbar-color: var(--sw-color-border-strong) transparent;
|
|
2420
|
+
-webkit-mask-image: linear-gradient(to bottom, black calc(100% - 28px), transparent 100%);
|
|
2421
|
+
mask-image: linear-gradient(to bottom, black calc(100% - 28px), transparent 100%);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
.sw-scroll-box::-webkit-scrollbar { width: 6px; }
|
|
2425
|
+
.sw-scroll-box::-webkit-scrollbar-track { background: transparent; }
|
|
2426
|
+
.sw-scroll-box::-webkit-scrollbar-thumb {
|
|
2427
|
+
background: var(--sw-color-border-strong);
|
|
2428
|
+
border-radius: var(--sw-radius-sm);
|
|
2429
|
+
}
|
|
2430
|
+
.sw-scroll-box::-webkit-scrollbar-thumb:hover {
|
|
2431
|
+
background: var(--sw-color-text-muted);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
/* ===========================================
|
|
2435
|
+
Score Table
|
|
2436
|
+
=========================================== */
|
|
2437
|
+
.score-table {
|
|
2438
|
+
width: 100%;
|
|
2439
|
+
border-collapse: collapse;
|
|
2440
|
+
margin: 12px 0;
|
|
2441
|
+
font-size: var(--sw-font-size-sm);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
.score-table th,
|
|
2445
|
+
.score-table td {
|
|
2446
|
+
padding: 8px 12px;
|
|
2447
|
+
text-align: left;
|
|
2448
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
.score-table th {
|
|
2452
|
+
background: var(--sw-color-bg);
|
|
2453
|
+
font-weight: 600;
|
|
2454
|
+
color: var(--sw-color-text);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
.score-cell {
|
|
2458
|
+
font-weight: bold;
|
|
2459
|
+
text-align: center !important;
|
|
2460
|
+
border-radius: var(--sw-radius-sm);
|
|
2461
|
+
width: 60px;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
.score-high {
|
|
2465
|
+
background: #d4edda;
|
|
2466
|
+
color: #155724;
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
.score-medium {
|
|
2470
|
+
background: #fff3cd;
|
|
2471
|
+
color: #856404;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
.score-low {
|
|
2475
|
+
background: #f8d7da;
|
|
2476
|
+
color: #721c24;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
.score-meaning {
|
|
2480
|
+
color: var(--sw-color-text-muted);
|
|
2481
|
+
font-style: italic;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/* ===========================================
|
|
2485
|
+
Status Badge
|
|
2486
|
+
|
|
2487
|
+
"status-badge"/"status-badge-*" are the legacy,
|
|
2488
|
+
unprefixed hooks (still styled for back-compat --
|
|
2489
|
+
deprecated, removed at 1.0; this generic name is the
|
|
2490
|
+
exact collision stream_weaver-lyb was filed for).
|
|
2491
|
+
"sw-status-badge"/"sw-status-badge--*"/"sw-status-badge__*"
|
|
2492
|
+
are the documented stable hooks (stream_weaver-oeo).
|
|
2493
|
+
=========================================== */
|
|
2494
|
+
.status-badge, .sw-status-badge {
|
|
2495
|
+
display: inline-flex;
|
|
2496
|
+
align-items: center;
|
|
2497
|
+
gap: var(--sw-spacing-xs);
|
|
2498
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
2499
|
+
border-radius: var(--sw-radius-sm);
|
|
2500
|
+
font-size: var(--sw-font-size-sm);
|
|
2501
|
+
margin: var(--sw-spacing-xs) var(--sw-spacing-sm) var(--sw-spacing-xs) 0;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
.status-badge-strong, .sw-status-badge--strong {
|
|
2505
|
+
background-color: rgba(16, 185, 129, 0.1);
|
|
2506
|
+
color: #059669;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.status-badge-maybe, .sw-status-badge--maybe {
|
|
2510
|
+
background-color: rgba(245, 158, 11, 0.1);
|
|
2511
|
+
color: #d97706;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
.status-badge-skip, .sw-status-badge--skip {
|
|
2515
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
2516
|
+
color: #dc2626;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
.status-badge-icon, .sw-status-badge__icon {
|
|
2520
|
+
font-size: 1em;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
.status-badge-label, .sw-status-badge__label {
|
|
2524
|
+
font-weight: 600;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
.status-badge-reasoning, .sw-status-badge__reasoning {
|
|
2528
|
+
color: var(--sw-color-text-muted);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
/* ===========================================
|
|
2532
|
+
Tag Buttons
|
|
2533
|
+
=========================================== */
|
|
2534
|
+
.tag-buttons {
|
|
2535
|
+
display: flex;
|
|
2536
|
+
flex-wrap: wrap;
|
|
2537
|
+
gap: var(--sw-spacing-xs);
|
|
2538
|
+
margin: var(--sw-spacing-sm) 0;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
.tag-btn {
|
|
2542
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
2543
|
+
border: 1px solid var(--sw-color-border);
|
|
2544
|
+
border-radius: var(--sw-radius-sm);
|
|
2545
|
+
background: var(--sw-color-bg-card);
|
|
2546
|
+
color: var(--sw-color-text);
|
|
2547
|
+
font-size: var(--sw-font-size-sm);
|
|
2548
|
+
cursor: pointer;
|
|
2549
|
+
transition: all var(--sw-transition);
|
|
2550
|
+
margin: 0;
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
.tag-btn:hover {
|
|
2554
|
+
border-color: var(--sw-color-primary);
|
|
2555
|
+
background: rgba(59, 130, 246, 0.05);
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
.tag-btn-selected {
|
|
2559
|
+
border-color: var(--sw-color-primary);
|
|
2560
|
+
background: var(--sw-color-primary);
|
|
2561
|
+
color: white;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
.tag-buttons-destructive .tag-btn:hover {
|
|
2565
|
+
border-color: #dc2626;
|
|
2566
|
+
background: rgba(220, 38, 38, 0.05);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
.tag-buttons-destructive .tag-btn-selected {
|
|
2570
|
+
border-color: #dc2626;
|
|
2571
|
+
background: #dc2626;
|
|
2572
|
+
color: white;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/* ===========================================
|
|
2576
|
+
External Link Button
|
|
2577
|
+
=========================================== */
|
|
2578
|
+
.external-link-btn {
|
|
2579
|
+
display: inline-flex;
|
|
2580
|
+
align-items: center;
|
|
2581
|
+
gap: var(--sw-spacing-xs);
|
|
2582
|
+
text-decoration: none;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
.external-link-btn::after {
|
|
2586
|
+
content: "↗";
|
|
2587
|
+
font-size: 0.8em;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
/* ===========================================
|
|
2591
|
+
Columns Layout
|
|
2592
|
+
=========================================== */
|
|
2593
|
+
.sw-columns {
|
|
2594
|
+
display: flex;
|
|
2595
|
+
gap: var(--sw-spacing-md);
|
|
2596
|
+
align-items: flex-start;
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
.sw-column {
|
|
2600
|
+
min-width: 0; /* Prevent flex items from overflowing */
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
.sw-column p {
|
|
2604
|
+
margin: var(--sw-spacing-xs) 0;
|
|
2605
|
+
color: var(--sw-color-text);
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
/* Monica-style sidebar sections */
|
|
2609
|
+
.sw-column.sidebar-facts {
|
|
2610
|
+
display: flex;
|
|
2611
|
+
flex-direction: column;
|
|
2612
|
+
gap: var(--sw-spacing-sm);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
/* "sidebar-section" is the legacy, unprefixed name (still
|
|
2616
|
+
styled for back-compat -- deprecated, removed at 1.0;
|
|
2617
|
+
this generic name is the exact collision stream_weaver-lyb
|
|
2618
|
+
was filed for, e.g. a real app's own ".sidebar-section").
|
|
2619
|
+
"sw-sidebar-section" is the documented stable hook
|
|
2620
|
+
(stream_weaver-oeo) -- apply it via `class: "sw-sidebar-section"`
|
|
2621
|
+
on any div/text call; there is no dedicated component for
|
|
2622
|
+
this utility class. */
|
|
2623
|
+
.sidebar-section, .sw-sidebar-section {
|
|
2624
|
+
background: var(--sw-color-bg-card);
|
|
2625
|
+
border: 1px solid var(--sw-color-border);
|
|
2626
|
+
border-radius: var(--sw-radius-md);
|
|
2627
|
+
padding: var(--sw-spacing-md);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
.sidebar-section h4, .sw-sidebar-section h4 {
|
|
2631
|
+
margin: 0 0 var(--sw-spacing-xs) 0;
|
|
2632
|
+
color: var(--sw-color-text-muted);
|
|
2633
|
+
font-size: 11px;
|
|
2634
|
+
font-weight: 600;
|
|
2635
|
+
text-transform: uppercase;
|
|
2636
|
+
letter-spacing: 0.05em;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
.sidebar-section p, .sw-sidebar-section p {
|
|
2640
|
+
margin: 0;
|
|
2641
|
+
color: var(--sw-color-text);
|
|
2642
|
+
font-size: var(--sw-font-size-sm);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
.sidebar-section a, .sw-sidebar-section a {
|
|
2646
|
+
color: var(--sw-color-primary);
|
|
2647
|
+
text-decoration: none;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
.sidebar-section a:hover, .sw-sidebar-section a:hover {
|
|
2651
|
+
text-decoration: underline;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
/* Responsive: Stack columns on mobile */
|
|
2655
|
+
@media (max-width: 640px) {
|
|
2656
|
+
.sw-columns {
|
|
2657
|
+
flex-direction: column;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
.sw-column {
|
|
2661
|
+
flex: none !important;
|
|
2662
|
+
width: 100% !important;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
/* ===========================================
|
|
2667
|
+
Utilities
|
|
2668
|
+
=========================================== */
|
|
2669
|
+
.todo-item {
|
|
2670
|
+
padding: var(--sw-spacing-sm);
|
|
2671
|
+
margin: var(--sw-spacing-xs) 0;
|
|
2672
|
+
background: var(--sw-color-bg);
|
|
2673
|
+
border-radius: var(--sw-radius-sm);
|
|
2674
|
+
display: flex;
|
|
2675
|
+
justify-content: space-between;
|
|
2676
|
+
align-items: center;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
/* Alpine.js cloak */
|
|
2680
|
+
[x-cloak] {
|
|
2681
|
+
display: none !important;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/* ===========================================
|
|
2685
|
+
Hover Effects (for div hover_class)
|
|
2686
|
+
=========================================== */
|
|
2687
|
+
.sw-hover-highlight {
|
|
2688
|
+
background: var(--sw-color-primary) !important;
|
|
2689
|
+
color: white !important;
|
|
2690
|
+
border-color: var(--sw-color-primary) !important;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
.sw-hover-lift {
|
|
2694
|
+
transform: translateY(-4px);
|
|
2695
|
+
box-shadow: var(--sw-shadow-lg);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
.sw-hover-glow {
|
|
2699
|
+
box-shadow: 0 0 20px var(--sw-color-primary);
|
|
2700
|
+
border-color: var(--sw-color-primary);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
/* Demo/example box for hover demonstrations */
|
|
2704
|
+
.hover-demo-box {
|
|
2705
|
+
padding: 2rem;
|
|
2706
|
+
border: 2px solid var(--sw-color-border);
|
|
2707
|
+
border-radius: var(--sw-radius-md);
|
|
2708
|
+
text-align: center;
|
|
2709
|
+
transition: all 0.2s ease;
|
|
2710
|
+
background: var(--sw-color-bg-card);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
/* Mobile touch support */
|
|
2714
|
+
@media (hover: none) {
|
|
2715
|
+
.sw-term, .term {
|
|
2716
|
+
cursor: pointer;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/* ===========================================
|
|
2721
|
+
Animations - Subtle, Non-distracting
|
|
2722
|
+
=========================================== */
|
|
2723
|
+
@keyframes fadeIn {
|
|
2724
|
+
from { opacity: 0; }
|
|
2725
|
+
to { opacity: 1; }
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/* Gentle page load */
|
|
2729
|
+
#app-container {
|
|
2730
|
+
animation: fadeIn 0.3s ease-out;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
/* Reduce motion for accessibility */
|
|
2734
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2735
|
+
*, *::before, *::after {
|
|
2736
|
+
animation-duration: 0.01ms !important;
|
|
2737
|
+
animation-iteration-count: 1 !important;
|
|
2738
|
+
transition-duration: 0.01ms !important;
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
/* ===========================================
|
|
2743
|
+
Code Editor (CodeMirror 5)
|
|
2744
|
+
=========================================== */
|
|
2745
|
+
.sw-code-editor-wrapper {
|
|
2746
|
+
border: 1px solid var(--sw-color-border);
|
|
2747
|
+
border-radius: var(--sw-radius-md);
|
|
2748
|
+
overflow: hidden;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
.sw-code-editor-wrapper .CodeMirror {
|
|
2752
|
+
height: 100%;
|
|
2753
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', 'Liberation Mono', monospace;
|
|
2754
|
+
font-size: 14px;
|
|
2755
|
+
line-height: 1.5;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
.sw-code-editor-wrapper .CodeMirror-gutters {
|
|
2759
|
+
background: var(--sw-color-bg);
|
|
2760
|
+
border-right: 1px solid var(--sw-color-border);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
.sw-code-editor-wrapper .CodeMirror-linenumber {
|
|
2764
|
+
color: var(--sw-color-text-muted);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
/* ===========================================
|
|
2768
|
+
Embedded Mode - Disable standalone styles
|
|
2769
|
+
Add class="sw-embedded" to body to use minimal styles
|
|
2770
|
+
=========================================== */
|
|
2771
|
+
body.sw-embedded {
|
|
2772
|
+
max-width: none;
|
|
2773
|
+
margin: 0;
|
|
2774
|
+
padding: 0;
|
|
2775
|
+
background: transparent;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
body.sw-embedded #app-container {
|
|
2779
|
+
background: transparent;
|
|
2780
|
+
padding: 0;
|
|
2781
|
+
border-radius: 0;
|
|
2782
|
+
box-shadow: none;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
/* ===========================================
|
|
2786
|
+
Dashboard Components (Cabinet Control style)
|
|
2787
|
+
=========================================== */
|
|
2788
|
+
|
|
2789
|
+
/* Status Dot - Colored indicator with optional glow */
|
|
2790
|
+
.sw-status-dot {
|
|
2791
|
+
display: inline-block;
|
|
2792
|
+
border-radius: 50%;
|
|
2793
|
+
flex-shrink: 0;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
.sw-status-dot-sm { width: 6px; height: 6px; }
|
|
2797
|
+
.sw-status-dot-md { width: 10px; height: 10px; }
|
|
2798
|
+
.sw-status-dot-lg { width: 14px; height: 14px; }
|
|
2799
|
+
|
|
2800
|
+
.sw-status-dot-red {
|
|
2801
|
+
background: var(--destructive);
|
|
2802
|
+
box-shadow: 0 0 8px color-mix(in oklch, var(--destructive) 40%, transparent);
|
|
2803
|
+
}
|
|
2804
|
+
.sw-status-dot-yellow {
|
|
2805
|
+
background: var(--warning);
|
|
2806
|
+
box-shadow: 0 0 8px color-mix(in oklch, var(--warning) 40%, transparent);
|
|
2807
|
+
}
|
|
2808
|
+
.sw-status-dot-green {
|
|
2809
|
+
background: var(--success);
|
|
2810
|
+
box-shadow: 0 0 8px color-mix(in oklch, var(--success) 40%, transparent);
|
|
2811
|
+
}
|
|
2812
|
+
.sw-status-dot-gray {
|
|
2813
|
+
background: var(--muted-foreground);
|
|
2814
|
+
opacity: 0.6;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
.sw-status-dot-pulse {
|
|
2818
|
+
animation: sw-pulse 2s ease-in-out infinite;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
@keyframes sw-pulse {
|
|
2822
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
2823
|
+
50% { opacity: 0.7; transform: scale(1.1); }
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
/* Badge - Count/label pills */
|
|
2827
|
+
.sw-badge {
|
|
2828
|
+
display: inline-flex;
|
|
2829
|
+
align-items: center;
|
|
2830
|
+
justify-content: center;
|
|
2831
|
+
font-family: 'JetBrains Mono', 'Monaco', 'Consolas', monospace;
|
|
2832
|
+
font-weight: 600;
|
|
2833
|
+
border-radius: 10px;
|
|
2834
|
+
white-space: nowrap;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
.sw-badge-sm { font-size: 0.65rem; padding: 0.15rem 0.4rem; }
|
|
2838
|
+
.sw-badge-md { font-size: 0.75rem; padding: 0.2rem 0.5rem; }
|
|
2839
|
+
|
|
2840
|
+
.sw-badge-default {
|
|
2841
|
+
background: var(--muted);
|
|
2842
|
+
color: var(--muted-foreground);
|
|
2843
|
+
}
|
|
2844
|
+
.sw-badge-danger {
|
|
2845
|
+
background: var(--destructive);
|
|
2846
|
+
color: var(--destructive-foreground);
|
|
2847
|
+
}
|
|
2848
|
+
.sw-badge-warning {
|
|
2849
|
+
background: var(--warning);
|
|
2850
|
+
color: var(--warning-foreground);
|
|
2851
|
+
}
|
|
2852
|
+
.sw-badge-success {
|
|
2853
|
+
background: var(--success);
|
|
2854
|
+
color: var(--success-foreground);
|
|
2855
|
+
}
|
|
2856
|
+
.sw-badge-info {
|
|
2857
|
+
background: var(--info);
|
|
2858
|
+
color: var(--info-foreground);
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
/* Stat Display - Large number with label */
|
|
2862
|
+
.sw-stat {
|
|
2863
|
+
text-align: center;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
.sw-stat-sm .sw-stat-value { font-size: 1.25rem; }
|
|
2867
|
+
.sw-stat-md .sw-stat-value { font-size: 1.5rem; }
|
|
2868
|
+
.sw-stat-lg .sw-stat-value { font-size: 2rem; }
|
|
2869
|
+
|
|
2870
|
+
.sw-stat-value {
|
|
2871
|
+
font-family: 'JetBrains Mono', 'Monaco', 'Consolas', monospace;
|
|
2872
|
+
font-weight: 600;
|
|
2873
|
+
line-height: 1.2;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
.sw-stat-value-default { color: var(--foreground); }
|
|
2877
|
+
.sw-stat-value-blue { color: #58a6ff; }
|
|
2878
|
+
.sw-stat-value-purple { color: #a371f7; }
|
|
2879
|
+
.sw-stat-value-green { color: #3fb950; }
|
|
2880
|
+
.sw-stat-value-red { color: #f85149; }
|
|
2881
|
+
.sw-stat-value-yellow { color: #d29922; }
|
|
2882
|
+
|
|
2883
|
+
.sw-stat-label {
|
|
2884
|
+
font-size: 0.65rem;
|
|
2885
|
+
text-transform: uppercase;
|
|
2886
|
+
letter-spacing: 0.05em;
|
|
2887
|
+
color: var(--muted-foreground);
|
|
2888
|
+
margin-top: 0.25rem;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
/* Type Tag - Activity type badges */
|
|
2892
|
+
.sw-type-tag {
|
|
2893
|
+
display: inline-block;
|
|
2894
|
+
font-size: 0.65rem;
|
|
2895
|
+
text-transform: uppercase;
|
|
2896
|
+
letter-spacing: 0.05em;
|
|
2897
|
+
padding: 0.2rem 0.5rem;
|
|
2898
|
+
border-radius: 4px;
|
|
2899
|
+
font-weight: 500;
|
|
2900
|
+
white-space: nowrap;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
.sw-type-tag-blue {
|
|
2904
|
+
background: rgba(88, 166, 255, 0.15);
|
|
2905
|
+
color: #58a6ff;
|
|
2906
|
+
}
|
|
2907
|
+
.sw-type-tag-green {
|
|
2908
|
+
background: rgba(63, 185, 80, 0.15);
|
|
2909
|
+
color: #3fb950;
|
|
2910
|
+
}
|
|
2911
|
+
.sw-type-tag-red {
|
|
2912
|
+
background: rgba(248, 81, 73, 0.15);
|
|
2913
|
+
color: #f85149;
|
|
2914
|
+
}
|
|
2915
|
+
.sw-type-tag-purple {
|
|
2916
|
+
background: rgba(163, 113, 247, 0.15);
|
|
2917
|
+
color: #a371f7;
|
|
2918
|
+
}
|
|
2919
|
+
.sw-type-tag-yellow {
|
|
2920
|
+
background: rgba(210, 153, 34, 0.15);
|
|
2921
|
+
color: #d29922;
|
|
2922
|
+
}
|
|
2923
|
+
.sw-type-tag-gray {
|
|
2924
|
+
background: rgba(139, 148, 158, 0.15);
|
|
2925
|
+
color: #8b949e;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
/* Pulse Indicator - Animated status with label */
|
|
2929
|
+
.sw-pulse-indicator {
|
|
2930
|
+
display: inline-flex;
|
|
2931
|
+
align-items: center;
|
|
2932
|
+
gap: 0.5rem;
|
|
2933
|
+
font-family: 'JetBrains Mono', 'Monaco', 'Consolas', monospace;
|
|
2934
|
+
font-size: 0.8rem;
|
|
2935
|
+
color: var(--muted-foreground);
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
.sw-pulse-dot {
|
|
2939
|
+
width: 8px;
|
|
2940
|
+
height: 8px;
|
|
2941
|
+
border-radius: 50%;
|
|
2942
|
+
animation: sw-pulse 2s ease-in-out infinite;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
.sw-pulse-dot-green { background: #3fb950; }
|
|
2946
|
+
.sw-pulse-dot-red { background: #f85149; }
|
|
2947
|
+
.sw-pulse-dot-yellow { background: #d29922; }
|
|
2948
|
+
.sw-pulse-dot-blue { background: #58a6ff; }
|
|
2949
|
+
|
|
2950
|
+
.sw-pulse-label {
|
|
2951
|
+
color: var(--muted-foreground);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
/* Priority Item - Escalation-style with colored border */
|
|
2955
|
+
.sw-priority-item {
|
|
2956
|
+
background: var(--sw-color-bg-elevated, #f5f5f5);
|
|
2957
|
+
border-radius: 8px;
|
|
2958
|
+
padding: 1rem;
|
|
2959
|
+
border-left: 3px solid;
|
|
2960
|
+
cursor: pointer;
|
|
2961
|
+
transition: all 0.2s ease;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
.sw-priority-item:hover {
|
|
2965
|
+
transform: translateX(4px);
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
.sw-priority-critical { border-left-color: #f85149; }
|
|
2969
|
+
.sw-priority-urgent { border-left-color: #d29922; }
|
|
2970
|
+
.sw-priority-high { border-left-color: #58a6ff; }
|
|
2971
|
+
.sw-priority-normal { border-left-color: #3fb950; }
|
|
2972
|
+
.sw-priority-low { border-left-color: #8b949e; }
|
|
2973
|
+
|
|
2974
|
+
.sw-priority-label {
|
|
2975
|
+
font-size: 0.6rem;
|
|
2976
|
+
text-transform: uppercase;
|
|
2977
|
+
letter-spacing: 0.08em;
|
|
2978
|
+
font-weight: 600;
|
|
2979
|
+
margin-bottom: 0.5rem;
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
.sw-priority-critical .sw-priority-label { color: #f85149; }
|
|
2983
|
+
.sw-priority-urgent .sw-priority-label { color: #d29922; }
|
|
2984
|
+
.sw-priority-high .sw-priority-label { color: #58a6ff; }
|
|
2985
|
+
.sw-priority-normal .sw-priority-label { color: #3fb950; }
|
|
2986
|
+
.sw-priority-low .sw-priority-label { color: #8b949e; }
|
|
2987
|
+
|
|
2988
|
+
.sw-priority-title {
|
|
2989
|
+
font-size: 0.85rem;
|
|
2990
|
+
font-weight: 500;
|
|
2991
|
+
margin: 0 0 0.35rem 0;
|
|
2992
|
+
color: var(--sw-color-text, #111);
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
.sw-priority-description {
|
|
2996
|
+
font-size: 0.75rem;
|
|
2997
|
+
color: var(--sw-color-text-muted, #666);
|
|
2998
|
+
line-height: 1.4;
|
|
2999
|
+
margin: 0 0 0.5rem 0;
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
.sw-priority-meta {
|
|
3003
|
+
display: flex;
|
|
3004
|
+
align-items: center;
|
|
3005
|
+
justify-content: space-between;
|
|
3006
|
+
font-size: 0.7rem;
|
|
3007
|
+
color: var(--sw-color-text-muted, #888);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
.sw-priority-meta-right {
|
|
3011
|
+
color: #58a6ff;
|
|
3012
|
+
font-weight: 500;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
/* Activity Item - Time + title + summary + type */
|
|
3016
|
+
.sw-activity-item {
|
|
3017
|
+
padding: 1rem;
|
|
3018
|
+
display: grid;
|
|
3019
|
+
grid-template-columns: auto 1fr auto;
|
|
3020
|
+
gap: 1rem;
|
|
3021
|
+
align-items: start;
|
|
3022
|
+
border-bottom: 1px solid var(--sw-color-border, #e0e0e0);
|
|
3023
|
+
transition: background 0.15s ease;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
.sw-activity-item:hover {
|
|
3027
|
+
background: var(--sw-color-bg-elevated, #f5f5f5);
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
.sw-activity-item:last-child {
|
|
3031
|
+
border-bottom: none;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
.sw-activity-time {
|
|
3035
|
+
font-family: 'JetBrains Mono', 'Monaco', 'Consolas', monospace;
|
|
3036
|
+
font-size: 0.7rem;
|
|
3037
|
+
color: var(--sw-color-text-muted, #888);
|
|
3038
|
+
white-space: nowrap;
|
|
3039
|
+
padding-top: 0.15rem;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
.sw-activity-content {
|
|
3043
|
+
min-width: 0;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
.sw-activity-title {
|
|
3047
|
+
font-size: 0.9rem;
|
|
3048
|
+
font-weight: 500;
|
|
3049
|
+
margin: 0 0 0.25rem 0;
|
|
3050
|
+
color: var(--sw-color-text, #111);
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
.sw-activity-summary {
|
|
3054
|
+
font-size: 0.8rem;
|
|
3055
|
+
color: var(--sw-color-text-muted, #666);
|
|
3056
|
+
line-height: 1.4;
|
|
3057
|
+
margin: 0;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/* ===========================================
|
|
3061
|
+
Timeline Event (Run Viewer style)
|
|
3062
|
+
=========================================== */
|
|
3063
|
+
|
|
3064
|
+
.sw-timeline-event {
|
|
3065
|
+
padding: 6px 10px;
|
|
3066
|
+
border-radius: 4px;
|
|
3067
|
+
cursor: pointer;
|
|
3068
|
+
display: flex;
|
|
3069
|
+
flex-wrap: wrap;
|
|
3070
|
+
align-items: center;
|
|
3071
|
+
gap: 8px;
|
|
3072
|
+
border-left: 3px solid transparent;
|
|
3073
|
+
font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
|
3074
|
+
font-size: 13px;
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
.sw-timeline-event:hover {
|
|
3078
|
+
background: var(--sw-color-bg-hover, #2a2a4a);
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
.sw-timeline-event__idx {
|
|
3082
|
+
color: #555;
|
|
3083
|
+
width: 24px;
|
|
3084
|
+
text-align: right;
|
|
3085
|
+
flex-shrink: 0;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
.sw-timeline-event__badge {
|
|
3089
|
+
display: inline-block;
|
|
3090
|
+
width: 100px;
|
|
3091
|
+
padding: 1px 6px;
|
|
3092
|
+
border-radius: 3px;
|
|
3093
|
+
text-align: center;
|
|
3094
|
+
font-size: 11px;
|
|
3095
|
+
flex-shrink: 0;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
.sw-timeline-event__ts {
|
|
3099
|
+
color: #666;
|
|
3100
|
+
flex-shrink: 0;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
.sw-timeline-event__label {
|
|
3104
|
+
color: #ccc;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
.sw-timeline-event__detail {
|
|
3108
|
+
width: 100%;
|
|
3109
|
+
padding: 10px 0 6px 36px;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
.sw-timeline-event__field {
|
|
3113
|
+
margin-bottom: 4px;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.sw-timeline-event__field-key {
|
|
3117
|
+
color: #aaa;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
.sw-timeline-event__field-value pre {
|
|
3121
|
+
margin: 4px 0 4px 12px;
|
|
3122
|
+
padding: 8px;
|
|
3123
|
+
background: #0d0d1a;
|
|
3124
|
+
border-radius: 4px;
|
|
3125
|
+
overflow-x: auto;
|
|
3126
|
+
white-space: pre-wrap;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
/* Type: phase — cyan */
|
|
3130
|
+
.sw-timeline-event--phase {
|
|
3131
|
+
border-left-color: #00bcd4;
|
|
3132
|
+
}
|
|
3133
|
+
.sw-timeline-event--phase .sw-timeline-event__badge {
|
|
3134
|
+
background: #0e3a3f;
|
|
3135
|
+
color: #00bcd4;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
/* Type: snapshot — yellow */
|
|
3139
|
+
.sw-timeline-event--snapshot {
|
|
3140
|
+
border-left-color: #ffc107;
|
|
3141
|
+
}
|
|
3142
|
+
.sw-timeline-event--snapshot .sw-timeline-event__badge {
|
|
3143
|
+
background: #3f3500;
|
|
3144
|
+
color: #ffc107;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
/* Type: intervention — purple */
|
|
3148
|
+
.sw-timeline-event--intervention {
|
|
3149
|
+
border-left-color: #ce93d8;
|
|
3150
|
+
}
|
|
3151
|
+
.sw-timeline-event--intervention .sw-timeline-event__badge {
|
|
3152
|
+
background: #3a1a44;
|
|
3153
|
+
color: #ce93d8;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
/* Type: timeout — red */
|
|
3157
|
+
.sw-timeline-event--timeout {
|
|
3158
|
+
border-left-color: #ef5350;
|
|
3159
|
+
}
|
|
3160
|
+
.sw-timeline-event--timeout .sw-timeline-event__badge {
|
|
3161
|
+
background: #3f0e0e;
|
|
3162
|
+
color: #ef5350;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
/* Type: guard — red */
|
|
3166
|
+
.sw-timeline-event--guard {
|
|
3167
|
+
border-left-color: #ef5350;
|
|
3168
|
+
}
|
|
3169
|
+
.sw-timeline-event--guard .sw-timeline-event__badge {
|
|
3170
|
+
background: #3f0e0e;
|
|
3171
|
+
color: #ef5350;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
/* Type: final — green */
|
|
3175
|
+
.sw-timeline-event--final {
|
|
3176
|
+
border-left-color: #66bb6a;
|
|
3177
|
+
}
|
|
3178
|
+
.sw-timeline-event--final .sw-timeline-event__badge {
|
|
3179
|
+
background: #0e3f12;
|
|
3180
|
+
color: #66bb6a;
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
/* ===========================================
|
|
3184
|
+
Layout Components (Cabinet Control style)
|
|
3185
|
+
=========================================== */
|
|
3186
|
+
|
|
3187
|
+
/* App Shell - Two-column layout */
|
|
3188
|
+
.sw-app-shell {
|
|
3189
|
+
display: grid;
|
|
3190
|
+
gap: var(--sw-shell-gap, 1.5rem);
|
|
3191
|
+
min-height: 100vh;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
.sw-app-shell-sidebar-right {
|
|
3195
|
+
grid-template-columns: 1fr var(--sw-shell-sidebar-width, 320px);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
.sw-app-shell-sidebar-left {
|
|
3199
|
+
grid-template-columns: var(--sw-shell-sidebar-width, 320px) 1fr;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
.sw-app-shell-sidebar-left .sw-app-shell-sidebar {
|
|
3203
|
+
order: -1;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
.sw-app-shell-main {
|
|
3207
|
+
min-width: 0;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
.sw-app-shell-sidebar {
|
|
3211
|
+
min-width: 0;
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
/* Sidebar */
|
|
3215
|
+
.sw-sidebar {
|
|
3216
|
+
display: flex;
|
|
3217
|
+
flex-direction: column;
|
|
3218
|
+
gap: var(--sw-spacing-md, 1rem);
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
.sw-sidebar-sticky {
|
|
3222
|
+
position: sticky;
|
|
3223
|
+
top: var(--sw-spacing-lg, 1.5rem);
|
|
3224
|
+
max-height: calc(100vh - var(--sw-spacing-xl, 3rem));
|
|
3225
|
+
overflow-y: auto;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
.sw-sidebar-header {
|
|
3229
|
+
display: flex;
|
|
3230
|
+
align-items: center;
|
|
3231
|
+
justify-content: space-between;
|
|
3232
|
+
margin-bottom: var(--sw-spacing-sm, 0.5rem);
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
.sw-sidebar-title {
|
|
3236
|
+
font-size: 0.85rem;
|
|
3237
|
+
font-weight: 600;
|
|
3238
|
+
text-transform: uppercase;
|
|
3239
|
+
letter-spacing: 0.05em;
|
|
3240
|
+
color: var(--muted-foreground);
|
|
3241
|
+
margin: 0;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
.sw-sidebar-content {
|
|
3245
|
+
display: flex;
|
|
3246
|
+
flex-direction: column;
|
|
3247
|
+
gap: var(--sw-spacing-sm, 0.5rem);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
/* Main Content */
|
|
3251
|
+
.sw-main-content {
|
|
3252
|
+
display: flex;
|
|
3253
|
+
flex-direction: column;
|
|
3254
|
+
gap: var(--sw-spacing-md, 1rem);
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
/* Expandable Card */
|
|
3258
|
+
.sw-expandable-card {
|
|
3259
|
+
background: var(--sw-color-bg-elevated, #f5f5f5);
|
|
3260
|
+
border: 1px solid var(--sw-color-border, #e0e0e0);
|
|
3261
|
+
border-radius: var(--sw-radius-lg, 10px);
|
|
3262
|
+
overflow: hidden;
|
|
3263
|
+
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
.sw-expandable-card:hover {
|
|
3267
|
+
box-shadow: var(--sw-shadow-md, 0 4px 8px rgba(0,0,0,0.1));
|
|
3268
|
+
transform: translateY(-2px);
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
.sw-expandable-card-header {
|
|
3272
|
+
display: flex;
|
|
3273
|
+
align-items: center;
|
|
3274
|
+
gap: 0.75rem;
|
|
3275
|
+
padding: 1rem 1.25rem;
|
|
3276
|
+
cursor: pointer;
|
|
3277
|
+
user-select: none;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
.sw-expandable-card-titles {
|
|
3281
|
+
flex: 1;
|
|
3282
|
+
min-width: 0;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
.sw-expandable-card-title {
|
|
3286
|
+
font-size: 1rem;
|
|
3287
|
+
font-weight: 600;
|
|
3288
|
+
margin: 0;
|
|
3289
|
+
color: var(--sw-color-text, #111);
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
.sw-expandable-card-subtitle {
|
|
3293
|
+
font-size: 0.8rem;
|
|
3294
|
+
color: var(--sw-color-text-muted, #666);
|
|
3295
|
+
margin: 0.25rem 0 0 0;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
.sw-expandable-card-chevron {
|
|
3299
|
+
font-size: 0.7rem;
|
|
3300
|
+
color: var(--sw-color-text-muted, #888);
|
|
3301
|
+
transition: transform 0.2s ease;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
.sw-expandable-card-body {
|
|
3305
|
+
padding: 0 1.25rem 1.25rem 1.25rem;
|
|
3306
|
+
border-top: 1px solid var(--sw-color-border, #e0e0e0);
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
/* Transition classes for Alpine.js */
|
|
3310
|
+
.sw-transition-enter {
|
|
3311
|
+
transition: all 0.2s ease-out;
|
|
3312
|
+
}
|
|
3313
|
+
.sw-transition-enter-start {
|
|
3314
|
+
opacity: 0;
|
|
3315
|
+
transform: translateY(-0.5rem);
|
|
3316
|
+
}
|
|
3317
|
+
.sw-transition-enter-end {
|
|
3318
|
+
opacity: 1;
|
|
3319
|
+
transform: translateY(0);
|
|
3320
|
+
}
|
|
3321
|
+
.sw-transition-leave {
|
|
3322
|
+
transition: all 0.15s ease-in;
|
|
3323
|
+
}
|
|
3324
|
+
.sw-transition-leave-start {
|
|
3325
|
+
opacity: 1;
|
|
3326
|
+
}
|
|
3327
|
+
.sw-transition-leave-end {
|
|
3328
|
+
opacity: 0;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
/* Responsive: Stack on smaller screens */
|
|
3332
|
+
@media (max-width: 900px) {
|
|
3333
|
+
.sw-app-shell {
|
|
3334
|
+
grid-template-columns: 1fr !important;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
.sw-app-shell-sidebar {
|
|
3338
|
+
order: 0 !important;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
.sw-sidebar-sticky {
|
|
3342
|
+
position: static;
|
|
3343
|
+
max-height: none;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
/* ===========================================
|
|
3348
|
+
Mobile Responsive
|
|
3349
|
+
=========================================== */
|
|
3350
|
+
|
|
3351
|
+
/* Tablet */
|
|
3352
|
+
@media (max-width: 900px) {
|
|
3353
|
+
body {
|
|
3354
|
+
padding: var(--sw-spacing-sm);
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
h1 { font-size: 1.75rem; }
|
|
3358
|
+
h2 { font-size: 1.35rem; }
|
|
3359
|
+
h3 { font-size: 1.15rem; }
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
/* Phone */
|
|
3363
|
+
@media (max-width: 640px) {
|
|
3364
|
+
body {
|
|
3365
|
+
padding: var(--sw-spacing-xs);
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
.card {
|
|
3369
|
+
border-radius: 0;
|
|
3370
|
+
padding: var(--sw-spacing-md);
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
.sw-scroll-box {
|
|
3374
|
+
padding: var(--sw-spacing-sm);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
h1 { font-size: 1.5rem; }
|
|
3378
|
+
h2 { font-size: 1.25rem; }
|
|
3379
|
+
h3 { font-size: 1.1rem; }
|
|
3380
|
+
|
|
3381
|
+
/* Tabs: horizontal scroll */
|
|
3382
|
+
.sw-tabs-list {
|
|
3383
|
+
overflow-x: auto;
|
|
3384
|
+
-webkit-overflow-scrolling: touch;
|
|
3385
|
+
scrollbar-width: none;
|
|
3386
|
+
flex-wrap: nowrap;
|
|
3387
|
+
}
|
|
3388
|
+
.sw-tabs-list::-webkit-scrollbar {
|
|
3389
|
+
display: none;
|
|
3390
|
+
}
|
|
3391
|
+
.sw-tab-trigger {
|
|
3392
|
+
white-space: nowrap;
|
|
3393
|
+
flex-shrink: 0;
|
|
3394
|
+
padding: var(--sw-spacing-xs) var(--sw-spacing-sm);
|
|
3395
|
+
font-size: var(--sw-font-size-sm);
|
|
3396
|
+
min-height: 44px;
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
/* HStacks: tighter gap */
|
|
3400
|
+
.sw-hstack {
|
|
3401
|
+
gap: var(--sw-spacing-xs);
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
/* Grid: single column override */
|
|
3405
|
+
.sw-grid {
|
|
3406
|
+
grid-template-columns: 1fr !important;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
/* Forms: prevent iOS auto-zoom, 44px touch targets */
|
|
3410
|
+
input[type="text"],
|
|
3411
|
+
input[type="email"],
|
|
3412
|
+
select,
|
|
3413
|
+
textarea {
|
|
3414
|
+
font-size: 16px;
|
|
3415
|
+
min-height: 44px;
|
|
3416
|
+
}
|
|
3417
|
+
button, .btn {
|
|
3418
|
+
min-height: 44px;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
/* Modals: full-screen on phones */
|
|
3422
|
+
.sw-modal {
|
|
3423
|
+
width: 100vw !important;
|
|
3424
|
+
max-width: 100vw !important;
|
|
3425
|
+
height: 100vh;
|
|
3426
|
+
max-height: 100vh;
|
|
3427
|
+
border-radius: 0;
|
|
3428
|
+
margin: 0;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
/* Toasts: full-width at top */
|
|
3432
|
+
.sw-toast-container {
|
|
3433
|
+
left: 0 !important;
|
|
3434
|
+
right: 0 !important;
|
|
3435
|
+
top: 0 !important;
|
|
3436
|
+
bottom: auto !important;
|
|
3437
|
+
width: 100%;
|
|
3438
|
+
padding: var(--sw-spacing-xs);
|
|
3439
|
+
}
|
|
3440
|
+
.sw-toast {
|
|
3441
|
+
border-radius: 0;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
/* Tables: horizontal scroll */
|
|
3445
|
+
.score-table {
|
|
3446
|
+
display: block;
|
|
3447
|
+
overflow-x: auto;
|
|
3448
|
+
-webkit-overflow-scrolling: touch;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
/* ===========================================
|
|
3453
|
+
Dark Theme (Cabinet Control style)
|
|
3454
|
+
=========================================== */
|
|
3455
|
+
body.sw-theme-dark {
|
|
3456
|
+
--sw-font-display: 'Outfit', 'Source Sans 3', system-ui, sans-serif;
|
|
3457
|
+
--sw-font-body: 'Outfit', 'Source Sans 3', system-ui, sans-serif;
|
|
3458
|
+
--sw-font-family: var(--sw-font-body);
|
|
3459
|
+
--sw-font-mono: 'JetBrains Mono', 'Monaco', 'Consolas', monospace;
|
|
3460
|
+
--sw-font-size-base: 15px;
|
|
3461
|
+
--sw-font-size-sm: 13px;
|
|
3462
|
+
--sw-font-size-lg: 17px;
|
|
3463
|
+
--sw-font-size-xl: 21px;
|
|
3464
|
+
--sw-line-height: 1.5;
|
|
3465
|
+
|
|
3466
|
+
/* Dark color palette */
|
|
3467
|
+
--sw-color-bg-deep: #0a0e14;
|
|
3468
|
+
--sw-color-bg: #0a0e14;
|
|
3469
|
+
--sw-color-bg-card: #131820;
|
|
3470
|
+
--sw-color-bg-elevated: #1a2029;
|
|
3471
|
+
--sw-color-bg-hover: #242d3a;
|
|
3472
|
+
--sw-color-border: #2a3544;
|
|
3473
|
+
--sw-color-text: #e6edf3;
|
|
3474
|
+
--sw-color-text-muted: #8b949e;
|
|
3475
|
+
--sw-color-text-light: #565d66;
|
|
3476
|
+
|
|
3477
|
+
/* Accent colors */
|
|
3478
|
+
--sw-color-primary: #58a6ff;
|
|
3479
|
+
--sw-color-primary-hover: #79b8ff;
|
|
3480
|
+
--sw-color-primary-light: rgba(88, 166, 255, 0.1);
|
|
3481
|
+
--sw-color-primary-glow: rgba(88, 166, 255, 0.2);
|
|
3482
|
+
--sw-color-accent: #a371f7;
|
|
3483
|
+
--sw-color-accent-light: rgba(163, 113, 247, 0.1);
|
|
3484
|
+
|
|
3485
|
+
/* Status colors */
|
|
3486
|
+
--sw-color-status-red: #f85149;
|
|
3487
|
+
--sw-color-status-yellow: #d29922;
|
|
3488
|
+
--sw-color-status-green: #3fb950;
|
|
3489
|
+
|
|
3490
|
+
/* Secondary */
|
|
3491
|
+
--sw-color-secondary: #8b949e;
|
|
3492
|
+
--sw-color-secondary-hover: #adbac7;
|
|
3493
|
+
--sw-color-secondary-foreground: #0a0e14;
|
|
3494
|
+
|
|
3495
|
+
/* Spacing */
|
|
3496
|
+
--sw-spacing-xs: 0.375rem;
|
|
3497
|
+
--sw-spacing-sm: 0.5rem;
|
|
3498
|
+
--sw-spacing-md: 0.875rem;
|
|
3499
|
+
--sw-spacing-lg: 1.25rem;
|
|
3500
|
+
--sw-spacing-xl: 1.75rem;
|
|
3501
|
+
--sw-spacing-2xl: 2.5rem;
|
|
3502
|
+
|
|
3503
|
+
/* Border radius */
|
|
3504
|
+
--sw-radius-sm: 4px;
|
|
3505
|
+
--sw-radius-md: 6px;
|
|
3506
|
+
--sw-radius-lg: 10px;
|
|
3507
|
+
--sw-radius-xl: 12px;
|
|
3508
|
+
|
|
3509
|
+
/* Shadows */
|
|
3510
|
+
--sw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
3511
|
+
--sw-shadow-md: 0 4px 8px rgba(0, 0, 0, 0.4);
|
|
3512
|
+
--sw-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
3513
|
+
--sw-shadow-xl: 0 12px 32px rgba(0, 0, 0, 0.6);
|
|
3514
|
+
--sw-shadow-inner: inset 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
3515
|
+
|
|
3516
|
+
/* Card styling */
|
|
3517
|
+
--sw-card-border-left: 3px solid var(--sw-color-accent);
|
|
3518
|
+
|
|
3519
|
+
/* Term highlighting */
|
|
3520
|
+
--sw-term-color: var(--sw-color-primary);
|
|
3521
|
+
--sw-term-bg-hover: var(--sw-color-primary-light);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
/* Dark theme base styles */
|
|
3525
|
+
body.sw-theme-dark {
|
|
3526
|
+
background: var(--sw-color-bg-deep);
|
|
3527
|
+
color: var(--sw-color-text);
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
body.sw-theme-dark[class*="sw-layout-"] > #app-container {
|
|
3531
|
+
background: var(--sw-color-bg-card);
|
|
3532
|
+
border-color: var(--sw-color-border);
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
body.sw-theme-dark h1,
|
|
3536
|
+
body.sw-theme-dark h2,
|
|
3537
|
+
body.sw-theme-dark h3,
|
|
3538
|
+
body.sw-theme-dark h4 {
|
|
3539
|
+
color: var(--sw-color-text);
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
body.sw-theme-dark h2 {
|
|
3543
|
+
border-bottom-color: var(--sw-color-border);
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
body.sw-theme-dark input[type="text"],
|
|
3547
|
+
body.sw-theme-dark input[type="email"],
|
|
3548
|
+
body.sw-theme-dark select,
|
|
3549
|
+
body.sw-theme-dark textarea {
|
|
3550
|
+
background: var(--sw-color-bg-elevated);
|
|
3551
|
+
border-color: var(--sw-color-border);
|
|
3552
|
+
color: var(--sw-color-text);
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
body.sw-theme-dark input[type="text"]:focus,
|
|
3556
|
+
body.sw-theme-dark input[type="email"]:focus,
|
|
3557
|
+
body.sw-theme-dark select:focus,
|
|
3558
|
+
body.sw-theme-dark textarea:focus {
|
|
3559
|
+
background: var(--sw-color-bg-hover);
|
|
3560
|
+
border-color: var(--sw-color-primary);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
body.sw-theme-dark .btn-primary {
|
|
3564
|
+
background: var(--sw-color-primary);
|
|
3565
|
+
color: white;
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
body.sw-theme-dark .btn-secondary {
|
|
3569
|
+
background: var(--sw-color-bg-elevated);
|
|
3570
|
+
color: var(--sw-color-text);
|
|
3571
|
+
border-color: var(--sw-color-border);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
/* Dark theme dashboard components */
|
|
3575
|
+
body.sw-theme-dark .sw-badge-default {
|
|
3576
|
+
background: var(--sw-color-bg-hover);
|
|
3577
|
+
color: var(--sw-color-text-muted);
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
body.sw-theme-dark .sw-priority-item {
|
|
3581
|
+
background: var(--sw-color-bg-elevated);
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
body.sw-theme-dark .sw-priority-item:hover {
|
|
3585
|
+
background: var(--sw-color-bg-hover);
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
body.sw-theme-dark .sw-activity-item:hover {
|
|
3589
|
+
background: var(--sw-color-bg-hover);
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
body.sw-theme-dark .sw-activity-item {
|
|
3593
|
+
border-bottom-color: var(--sw-color-border);
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
/* Dark theme layout components */
|
|
3597
|
+
body.sw-theme-dark .sw-expandable-card {
|
|
3598
|
+
background: var(--sw-color-bg-elevated);
|
|
3599
|
+
border-color: var(--sw-color-border);
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
body.sw-theme-dark .sw-expandable-card:hover {
|
|
3603
|
+
background: var(--sw-color-bg-hover);
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
body.sw-theme-dark .sw-expandable-card-body {
|
|
3607
|
+
border-top-color: var(--sw-color-border);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
/* Expandable card highlight (e.g. current timeline slot) */
|
|
3611
|
+
.sw-expandable-card--highlight {
|
|
3612
|
+
border-left: 4px solid var(--sw-color-primary, #3b82f6);
|
|
3613
|
+
background: color-mix(in srgb, var(--sw-color-primary, #3b82f6) 5%, var(--sw-color-bg-elevated, #f5f5f5));
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
body.sw-theme-dark .sw-expandable-card--highlight {
|
|
3617
|
+
background: color-mix(in srgb, var(--sw-color-primary, #3b82f6) 8%, var(--sw-color-bg-elevated));
|
|
3618
|
+
}
|
|
3619
|
+
CSS
|
|
3620
|
+
end
|
|
3621
|
+
|
|
3622
|
+
private
|
|
3623
|
+
|
|
3624
|
+
# Render all components
|
|
3625
|
+
def render_components
|
|
3626
|
+
@app.components.each do |component|
|
|
3627
|
+
component.render(self, @state)
|
|
3628
|
+
end
|
|
3629
|
+
|
|
3630
|
+
# Add submit button for agentic mode
|
|
3631
|
+
render_agentic_submit_button if @is_agentic
|
|
3632
|
+
end
|
|
3633
|
+
|
|
3634
|
+
# Render the submit button for agentic mode
|
|
3635
|
+
def render_agentic_submit_button
|
|
3636
|
+
div(style: "margin-top: 30px; padding-top: 20px; border-top: 2px solid #e0e0e0;") do
|
|
3637
|
+
p(style: "color: #666; font-size: 14px;") { "Submit this form to return data to the calling agent:" }
|
|
3638
|
+
button(
|
|
3639
|
+
type: "button",
|
|
3640
|
+
class: "btn btn-primary",
|
|
3641
|
+
style: "background: #28a745; font-weight: bold;",
|
|
3642
|
+
"hx-post" => "/submit",
|
|
3643
|
+
"hx-include" => @adapter.input_selector
|
|
3644
|
+
) { "🤖 Submit to Agent" }
|
|
3645
|
+
end
|
|
3646
|
+
end
|
|
3647
|
+
|
|
3648
|
+
# Generate body classes for layout and theme.
|
|
3649
|
+
# Exclusive layouts supply their own body_classes; the sw-layout-* class is omitted.
|
|
3650
|
+
def body_classes
|
|
3651
|
+
effective_theme = @session_theme || @app.theme
|
|
3652
|
+
theme_class = "sw-theme-#{effective_theme}"
|
|
3653
|
+
|
|
3654
|
+
layout_entry = @app.layout_entry
|
|
3655
|
+
if layout_entry&.dig(:exclusive)
|
|
3656
|
+
extra = layout_entry[:body_classes].join(" ")
|
|
3657
|
+
[extra, theme_class].reject(&:empty?).join(" ")
|
|
3658
|
+
else
|
|
3659
|
+
"sw-layout-#{@app.layout} #{theme_class}"
|
|
3660
|
+
end
|
|
3661
|
+
end
|
|
3662
|
+
|
|
3663
|
+
# Get the effective theme (session override or app default)
|
|
3664
|
+
def effective_theme
|
|
3665
|
+
@session_theme || @app.theme
|
|
3666
|
+
end
|
|
3667
|
+
|
|
3668
|
+
# Render inline CSS for theme overrides.
|
|
3669
|
+
#
|
|
3670
|
+
# Scoped to body.sw-theme-<active theme> rather than a bare `body`
|
|
3671
|
+
# selector: every built-in theme's own CSS uses that same
|
|
3672
|
+
# body.sw-theme-<name> selector (specificity 0-1-1), which always
|
|
3673
|
+
# beats a plain `body {}` (0-0-1) regardless of source order. Matching
|
|
3674
|
+
# the selector's specificity means the tie is broken by cascade order
|
|
3675
|
+
# instead -- this block is emitted in <body>, after the built-in
|
|
3676
|
+
# theme's own block in <head>, so it wins (stream_weaver-ckz; same
|
|
3677
|
+
# technique as the `html.dark body` dark-mode override above).
|
|
3678
|
+
def render_theme_overrides
|
|
3679
|
+
warn_unknown_theme_override_tokens
|
|
3680
|
+
css_vars = @app.theme_overrides.map do |key, value|
|
|
3681
|
+
css_var = key.to_s.tr('_', '-')
|
|
3682
|
+
# Add sw- prefix if not present
|
|
3683
|
+
css_var = "sw-#{css_var}" unless css_var.start_with?('sw-')
|
|
3684
|
+
"--#{css_var}: #{value};"
|
|
3685
|
+
end.join("\n ")
|
|
3686
|
+
|
|
3687
|
+
style do
|
|
3688
|
+
raw(safe(StreamWeaver::CSS.layer_wrap("body.sw-theme-#{effective_theme} { #{css_vars} }")))
|
|
3689
|
+
end
|
|
3690
|
+
end
|
|
3691
|
+
|
|
3692
|
+
def warn_unknown_theme_override_tokens
|
|
3693
|
+
unknown = @app.theme_overrides.keys.reject { |key| Theme::VARIABLE_SCHEMA.key?(key.to_sym) }
|
|
3694
|
+
return if unknown.empty?
|
|
3695
|
+
|
|
3696
|
+
warn "StreamWeaver: Unknown theme override token(s) #{unknown.map(&:inspect).join(', ')} -- " \
|
|
3697
|
+
"no matching entry in Theme::VARIABLE_SCHEMA, so this override is emitted as a raw --sw- custom property " \
|
|
3698
|
+
"that no built-in styling reads."
|
|
3699
|
+
end
|
|
3700
|
+
|
|
3701
|
+
# Render CSS for custom registered themes
|
|
3702
|
+
def render_custom_theme_css
|
|
3703
|
+
theme_name = effective_theme
|
|
3704
|
+
# Only render if it's a custom theme (not built-in)
|
|
3705
|
+
return if StreamWeaver::App::BUILT_IN_THEMES.include?(theme_name)
|
|
3706
|
+
|
|
3707
|
+
custom_theme = StreamWeaver.get_theme(theme_name)
|
|
3708
|
+
return unless custom_theme
|
|
3709
|
+
|
|
3710
|
+
style do
|
|
3711
|
+
raw(safe(StreamWeaver::CSS.layer_wrap(custom_theme.to_css)))
|
|
3712
|
+
end
|
|
3713
|
+
end
|
|
3714
|
+
|
|
3715
|
+
# Emit CSS/JS declared via the css/css_path/js_path class macros on component classes.
|
|
3716
|
+
# Deduped by component class so multiple instances only emit their assets once.
|
|
3717
|
+
# Also collects from layout slots and emits the layout's own css_path if registered.
|
|
3718
|
+
def render_component_assets
|
|
3719
|
+
all_components = @app.components + (@app.layout_slots || {}).values.flatten
|
|
3720
|
+
|
|
3721
|
+
# Layout-level CSS file
|
|
3722
|
+
layout_entry = @app.layout_entry
|
|
3723
|
+
if layout_entry&.dig(:css_path)
|
|
3724
|
+
path = layout_entry[:css_path]
|
|
3725
|
+
key = ComponentAssets.file_key(path)
|
|
3726
|
+
link(rel: "stylesheet", href: "/sw-asset/#{key}/#{File.basename(path)}")
|
|
3727
|
+
end
|
|
3728
|
+
|
|
3729
|
+
css_strings, css_paths, js_paths = ComponentAssets.collect(all_components)
|
|
3730
|
+
|
|
3731
|
+
css_strings.each { |css| style { raw(safe(css)) } }
|
|
3732
|
+
css_paths.each do |path|
|
|
3733
|
+
key = ComponentAssets.file_key(path)
|
|
3734
|
+
link(rel: "stylesheet", href: "/sw-asset/#{key}/#{File.basename(path)}")
|
|
3735
|
+
end
|
|
3736
|
+
js_paths.each do |path|
|
|
3737
|
+
key = ComponentAssets.file_key(path)
|
|
3738
|
+
script(src: "/sw-asset/#{key}/#{File.basename(path)}")
|
|
3739
|
+
end
|
|
3740
|
+
end
|
|
3741
|
+
|
|
3742
|
+
# Render all components in a named layout slot.
|
|
3743
|
+
# Used inside register_layout render blocks.
|
|
3744
|
+
def render_slot(name)
|
|
3745
|
+
((@app.layout_slots || {})[name] || []).each { |c| c.render(self, @state) }
|
|
3746
|
+
end
|
|
3747
|
+
|
|
3748
|
+
# Emit the main reactive content region (#app-container).
|
|
3749
|
+
# Use this inside a register_layout render block to place the reactive app content.
|
|
3750
|
+
def main_content_region
|
|
3751
|
+
div(id: "app-container", "data-sw-state-version" => @app.render_state.state_version,
|
|
3752
|
+
**@adapter.container_attributes(@state)) do
|
|
3753
|
+
render_components
|
|
3754
|
+
end
|
|
3755
|
+
end
|
|
3756
|
+
end
|
|
3757
|
+
|
|
3758
|
+
# Partial view for HTMX updates (just the app-container content)
|
|
3759
|
+
# Includes state data for Alpine.js reinitialization after HTMX swap
|
|
3760
|
+
class AppContentView < Phlex::HTML
|
|
3761
|
+
attr_reader :adapter, :app
|
|
3762
|
+
attr_reader :current_fragment_id
|
|
3763
|
+
|
|
3764
|
+
# @param app [StreamWeaver::App] The app instance
|
|
3765
|
+
# @param state [Hash] The current state
|
|
3766
|
+
# @param adapter [StreamWeaver::Adapter::Base] The adapter for rendering
|
|
3767
|
+
# @param is_agentic [Boolean] Whether running in agentic mode
|
|
3768
|
+
def initialize(app, state, adapter, is_agentic = false)
|
|
3769
|
+
@app = app
|
|
3770
|
+
@state = state
|
|
3771
|
+
@adapter = adapter
|
|
3772
|
+
@is_agentic = is_agentic
|
|
3773
|
+
end
|
|
3774
|
+
|
|
3775
|
+
def view_template
|
|
3776
|
+
# Include fresh state data for Alpine.js reinitialization
|
|
3777
|
+
# This allows JavaScript to update the outer container's x-data after HTMX swap
|
|
3778
|
+
# See: Alpine.js Defer Mutations Pattern in adapter/alpinejs.rb
|
|
3779
|
+
state_json_data = @state.transform_keys(&:to_s)
|
|
3780
|
+
state_json_data["_sw_version"] = @app.render_state.state_version
|
|
3781
|
+
if @app.respond_to?(:transient_keys) && @app.transient_keys.any?
|
|
3782
|
+
state_json_data["_transient"] = @app.transient_keys.map(&:to_s)
|
|
3783
|
+
end
|
|
3784
|
+
state_json = JSON.generate(state_json_data)
|
|
3785
|
+
script(type: "application/json", id: "sw-state-data") { raw safe(state_json) }
|
|
3786
|
+
|
|
3787
|
+
@app.components.each do |component|
|
|
3788
|
+
component.render(self, @state)
|
|
3789
|
+
end
|
|
3790
|
+
|
|
3791
|
+
# Add submit button for agentic mode
|
|
3792
|
+
render_agentic_submit_button if @is_agentic
|
|
3793
|
+
end
|
|
3794
|
+
|
|
3795
|
+
def with_fragment(id)
|
|
3796
|
+
previous = @current_fragment_id
|
|
3797
|
+
@current_fragment_id = id
|
|
3798
|
+
yield
|
|
3799
|
+
ensure
|
|
3800
|
+
@current_fragment_id = previous
|
|
3801
|
+
end
|
|
3802
|
+
|
|
3803
|
+
private
|
|
3804
|
+
|
|
3805
|
+
# Render the submit button for agentic mode
|
|
3806
|
+
def render_agentic_submit_button
|
|
3807
|
+
div(style: "margin-top: 30px; padding-top: 20px; border-top: 2px solid #e0e0e0;") do
|
|
3808
|
+
p(style: "color: #666; font-size: 14px;") { "Submit this form to return data to the calling agent:" }
|
|
3809
|
+
button(
|
|
3810
|
+
type: "button",
|
|
3811
|
+
class: "btn btn-primary",
|
|
3812
|
+
style: "background: #28a745; font-weight: bold;",
|
|
3813
|
+
"hx-post" => "/submit",
|
|
3814
|
+
"hx-include" => @adapter.input_selector
|
|
3815
|
+
) { "🤖 Submit to Agent" }
|
|
3816
|
+
end
|
|
3817
|
+
end
|
|
3818
|
+
end
|
|
3819
|
+
|
|
3820
|
+
# Shared by FragmentContentView and RowSwapView: renders one declared
|
|
3821
|
+
# `updates:` fragment as an OOB element. Row-granular narrowing
|
|
3822
|
+
# (stream_weaver-95k) applies independently per extra -- a toolbar button
|
|
3823
|
+
# whose own fragment has nothing to narrow can still narrow an extra
|
|
3824
|
+
# fragment's table (the common "Add" button outside the table, `updates:`
|
|
3825
|
+
# pointing at the table's fragment, is exactly this shape). Falls back to
|
|
3826
|
+
# the full-fragment OOB swap (pre-existing FAC-P1 behavior) whenever
|
|
3827
|
+
# `swap` is nil -- i.e. that extra's mutation wasn't provably row-local.
|
|
3828
|
+
# OOB elements always use htmx's native swap styles (plain innerHTML/
|
|
3829
|
+
# outerHTML), never an extension-only style like "morph:innerHTML" --
|
|
3830
|
+
# htmx core's own hx-swap-oob scanning doesn't dispatch through swap-style
|
|
3831
|
+
# extensions, so those are silently skipped in a real browser
|
|
3832
|
+
# (stream_weaver-e4p). Morph stays available for the PRIMARY target only.
|
|
3833
|
+
module ExtraFragmentRendering
|
|
3834
|
+
def render_extra(fragment, swap)
|
|
3835
|
+
return render_row_oob(swap) if swap
|
|
3836
|
+
|
|
3837
|
+
div(id: fragment.id, "hx-swap-oob" => "innerHTML") do
|
|
3838
|
+
with_fragment(fragment.id) { fragment.children.each { |child| child.render(self, @state) } }
|
|
3839
|
+
end
|
|
3840
|
+
end
|
|
3841
|
+
|
|
3842
|
+
def render_row_oob(swap)
|
|
3843
|
+
table = swap[:table]
|
|
3844
|
+
case swap[:kind]
|
|
3845
|
+
when :edit
|
|
3846
|
+
adapter.render_table_row(self, table.resolved_rows[swap[:idx]], swap[:idx], table.table_options, @state, table.dom_id,
|
|
3847
|
+
extra_attrs: { "hx-swap-oob" => "outerHTML" })
|
|
3848
|
+
when :delete
|
|
3849
|
+
tr(id: swap[:row_dom_id], "hx-swap-oob" => "delete")
|
|
3850
|
+
when :create
|
|
3851
|
+
adapter.render_table_row(self, table.resolved_rows[swap[:idx]], swap[:idx], table.table_options, @state, table.dom_id,
|
|
3852
|
+
extra_attrs: { "hx-swap-oob" => "beforeend:##{table.dom_id} tbody" })
|
|
3853
|
+
end
|
|
3854
|
+
end
|
|
3855
|
+
end
|
|
3856
|
+
|
|
3857
|
+
class FragmentContentView < AppContentView
|
|
3858
|
+
include ExtraFragmentRendering
|
|
3859
|
+
|
|
3860
|
+
# @param extra_swaps [Array<Hash, nil>] One row-swap analysis (or nil) per
|
|
3861
|
+
# entry in `updates:`, positionally aligned (stream_weaver-95k)
|
|
3862
|
+
def initialize(app, state, adapter, fragment, updates: [], extra_swaps: [], state_patch:)
|
|
3863
|
+
super(app, state, adapter, false)
|
|
3864
|
+
@fragment = fragment
|
|
3865
|
+
@updates = updates
|
|
3866
|
+
@extra_swaps = extra_swaps
|
|
3867
|
+
@state_patch = state_patch
|
|
3868
|
+
end
|
|
3869
|
+
|
|
3870
|
+
def view_template
|
|
3871
|
+
with_fragment(@fragment.id) { @fragment.children.each { |child| child.render(self, @state) } }
|
|
3872
|
+
@updates.each_with_index { |fragment, i| render_extra(fragment, @extra_swaps[i]) }
|
|
3873
|
+
# nil for a deferred fragment's auto-fetch, which claims no state
|
|
3874
|
+
# version (see InteractionRunner#deferred_fetch?).
|
|
3875
|
+
raw safe(StatePatchView.new(@state_patch).call) if @state_patch
|
|
3876
|
+
end
|
|
3877
|
+
end
|
|
3878
|
+
|
|
3879
|
+
# Row-granular counterpart to FragmentContentView (stream_weaver-95k): renders
|
|
3880
|
+
# just the mutated row's content (or nothing, for a delete) instead of the
|
|
3881
|
+
# whole fragment. The caller retargets/reswaps the *primary* content via
|
|
3882
|
+
# HX-Retarget/HX-Reswap response headers (InteractionRunner picks the row's
|
|
3883
|
+
# own id, or the table's tbody, as the target) rather than hx-swap-oob on the
|
|
3884
|
+
# primary content itself -- that reuses the exact header mechanism the runner
|
|
3885
|
+
# already has for full-view fallback, and keeps the primary swap composable
|
|
3886
|
+
# with htmx's normal target/swap-style handling (morph, delete, beforeend)
|
|
3887
|
+
# instead of hand-rolling those semantics via OOB attributes.
|
|
3888
|
+
# Declared `updates:` extras and the state patch still need hx-swap-oob,
|
|
3889
|
+
# since they are never part of the (retargeted) primary swap zone.
|
|
3890
|
+
class RowSwapView < AppContentView
|
|
3891
|
+
include ExtraFragmentRendering
|
|
3892
|
+
|
|
3893
|
+
# @param app [StreamWeaver::App] The app instance
|
|
3894
|
+
# @param state [Hash] The current state
|
|
3895
|
+
# @param adapter [StreamWeaver::Adapter::Base] The adapter for rendering
|
|
3896
|
+
# @param updates [Array<Components::Fragment>] Declared `updates:` fragments, rendered OOB
|
|
3897
|
+
# @param extra_swaps [Array<Hash, nil>] One row-swap analysis (or nil) per entry in `updates:`
|
|
3898
|
+
# @param state_patch [Hash] The state patch (see InteractionRunner#state_patch)
|
|
3899
|
+
# @param primary [Proc, nil] Renders the primary (retargeted) content into the view; nil for a delete (empty body)
|
|
3900
|
+
def initialize(app, state, adapter, updates:, state_patch:, extra_swaps: [], primary: nil)
|
|
3901
|
+
super(app, state, adapter, false)
|
|
3902
|
+
@updates = updates
|
|
3903
|
+
@extra_swaps = extra_swaps
|
|
3904
|
+
@state_patch = state_patch
|
|
3905
|
+
@primary = primary
|
|
3906
|
+
end
|
|
3907
|
+
|
|
3908
|
+
def view_template
|
|
3909
|
+
@primary&.call(self)
|
|
3910
|
+
@updates.each_with_index { |fragment, i| render_extra(fragment, @extra_swaps[i]) }
|
|
3911
|
+
raw safe(StatePatchView.new(@state_patch, true).call)
|
|
3912
|
+
end
|
|
3913
|
+
end
|
|
3914
|
+
|
|
3915
|
+
class StatePatchView < Phlex::HTML
|
|
3916
|
+
# `oob` is positional, not a keyword arg: existing call sites pass the
|
|
3917
|
+
# patch hash bare (`StatePatchView.new(set: ..., delete: ..., version: ...)`),
|
|
3918
|
+
# relying on Ruby folding an un-braced trailing hash into the sole
|
|
3919
|
+
# positional `patch` param. Declaring `oob:` as a keyword instead would
|
|
3920
|
+
# make Ruby try to parse that call's hash as keyword arguments (it isn't).
|
|
3921
|
+
def initialize(patch, oob = false)
|
|
3922
|
+
@patch = patch
|
|
3923
|
+
@oob = oob
|
|
3924
|
+
end
|
|
3925
|
+
|
|
3926
|
+
def view_template
|
|
3927
|
+
json = JSON.generate(@patch).gsub("<", "\\u003c")
|
|
3928
|
+
attrs = { type: "application/json", id: "sw-state-patch" }
|
|
3929
|
+
# RowSwapView's primary swap zone is a row/tbody, not the fragment, so
|
|
3930
|
+
# the patch script can't ride along inside the primary content like it
|
|
3931
|
+
# does for FragmentContentView -- it needs its own OOB delivery.
|
|
3932
|
+
# beforeend into #app-container (not an id-match OOB swap) so this
|
|
3933
|
+
# doesn't depend on a `#sw-state-patch` element already existing in
|
|
3934
|
+
# the DOM (stream_weaver-95k).
|
|
3935
|
+
attrs["hx-swap-oob"] = "beforeend:#app-container" if @oob
|
|
3936
|
+
script(**attrs) { raw safe(json) }
|
|
3937
|
+
end
|
|
3938
|
+
end
|
|
3939
|
+
end
|
|
3940
|
+
end
|