diff --git a/AGENTS.md b/AGENTS.md index 910fced..2830fea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -209,6 +209,11 @@ labhelper/ │ │ ├── clean_orphaned_files.py # Cleanup orphaned ThingFile attachments │ │ └── clean_orphaned_images.py # Cleanup orphaned Thing images │ ├── migrations/ # Database migrations +│ ├── static/ +│ │ └── css/ +│ │ ├── base.css # Base styles (layout, navbar, buttons, alerts, etc.) +│ │ ├── edit_thing.css # Edit thing form styles +│ │ └── thing_detail.css # Markdown content + lightbox styles │ ├── templates/ │ │ └── boxes/ │ │ ├── add_things.html # Form to add multiple things @@ -356,14 +361,21 @@ The project uses a base template system at `labhelper/templates/base.html`. All ### CSS Guidelines +**Static CSS Files:** +- Base styles live in `boxes/static/css/base.css` (loaded by `base.html` via `{% static %}`) +- Page-specific styles live in separate static CSS files (e.g., `thing_detail.css`, `edit_thing.css`) +- Child templates load their CSS via `{% block extra_css %}` with `` tags +- WhiteNoise serves and cache-busts static files via `CompressedManifestStaticFilesStorage` +- Run `python manage.py collectstatic` after adding or modifying static CSS files + **Naming:** - Use descriptive class names - BEM pattern encouraged for complex components -- Inline styles allowed for template-specific styling +- Inline styles allowed for template-specific one-off styling **Styles:** -- Use base template styles when possible -- Template-specific styles in `{% block extra_css %}` +- Use base CSS classes when possible +- Page-specific styles in dedicated static CSS files loaded via `{% block extra_css %}` - JavaScript in `{% block extra_js %}` - Smooth transitions (0.2s - 0.3s) - Hover effects with transform and box-shadow @@ -410,36 +422,44 @@ The project uses a base template system at `labhelper/templates/base.html`. All - `title`: Page title tag - `page_header`: Page header with breadcrumbs - `content`: Main page content - - `extra_css`: Additional styles + - `extra_css`: Additional CSS via `` tags to static files + - `extra_head`: Additional head elements - `extra_js`: Additional JavaScript 3. **Load required template tags** ```django - {% load static %} + {% load static %} {# Required when using {% static %} for CSS/asset links #} {% load mptt_tags %} {% load thumbnail %} {% load dict_extras %} ``` -4. **Use URL names for links** +4. **Link page-specific CSS from static files** + ```django + {% block extra_css %} + + {% endblock %} + ``` + +5. **Use URL names for links** ```django ``` -5. **Use icons with Font Awesome** +6. **Use icons with Font Awesome** ```django ``` -6. **Add breadcrumbs for navigation** +7. **Add breadcrumbs for navigation** ```django ``` -7. **Icon alignment in lists**: When using icons in list items, use fixed width containers to ensure proper alignment +8. **Icon alignment in lists**: When using icons in list items, use fixed width containers to ensure proper alignment ```django diff --git a/argocd/configmap.yaml b/argocd/configmap.yaml index 7c7c364..c671106 100644 --- a/argocd/configmap.yaml +++ b/argocd/configmap.yaml @@ -18,4 +18,4 @@ data: LOGIN_REDIRECT_URL: "index" LOGOUT_REDIRECT_URL: "login" GUNICORN_OPTS: "--access-logfile -" - IMAGE_TAG: "0.074" + IMAGE_TAG: "0.075" diff --git a/argocd/deployment.yaml b/argocd/deployment.yaml index 0ab0449..f8dd2e8 100644 --- a/argocd/deployment.yaml +++ b/argocd/deployment.yaml @@ -27,7 +27,7 @@ spec: mountPath: /data containers: - name: web - image: git.baumann.gr/adebaumann/labhelper:0.074 + image: git.baumann.gr/adebaumann/labhelper:0.075 imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/boxes/static/css/base.css b/boxes/static/css/base.css new file mode 100644 index 0000000..99bf447 --- /dev/null +++ b/boxes/static/css/base.css @@ -0,0 +1,378 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + padding: 0 20px; +} + +.navbar { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 15px; + padding: 15px 30px; + margin: 20px auto; + max-width: 1200px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 15px; +} + +.navbar-brand { + font-size: 28px; + font-weight: 700; + color: #667eea; + text-decoration: none; + display: flex; + align-items: center; + gap: 10px; +} + +.navbar-brand i { + font-size: 24px; +} + +.navbar-toggle { + display: none; + background: none; + border: none; + color: #555; + font-size: 24px; + cursor: pointer; + padding: 10px; + border-radius: 8px; + transition: all 0.3s ease; +} + +.navbar-toggle:hover { + background: #667eea; + color: white; +} + +.navbar-nav { + display: flex; + gap: 20px; + align-items: center; +} + +.navbar-nav a, +.navbar-nav form { + color: #555; + text-decoration: none; + font-weight: 500; + font-size: 15px; + padding: 8px 16px; + border-radius: 8px; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.navbar-nav a:hover, +.navbar-nav button:hover { + background: #667eea; + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.navbar-nav a i { + font-size: 14px; +} + +.navbar-nav button { + background: none; + border: none; + color: #555; + font: inherit; + cursor: pointer; + padding: 8px 16px; + border-radius: 8px; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +@media (max-width: 768px) { + .navbar { + padding: 15px 20px; + } + + .navbar-brand { + font-size: 24px; + } + + .navbar-toggle { + display: block; + } + + .navbar-nav { + display: none; + width: 100%; + flex-direction: column; + gap: 0; + padding-top: 10px; + } + + .navbar-nav.active { + display: flex; + } + + .navbar-nav a, + .navbar-nav form { + width: 100%; + padding: 12px 16px; + border-radius: 0; + } + + .navbar-nav a:first-child, + .navbar-nav form:first-child { + border-radius: 8px 8px 0 0; + } + + .navbar-nav a:last-child, + .navbar-nav form:last-child { + border-radius: 0 0 8px 8px; + } + + .navbar-nav button { + width: 100%; + justify-content: flex-start; + } +} + +.container { + max-width: 1200px; + margin: 20px auto; +} + +.page-header { + background: white; + padding: 30px; + border-radius: 15px; + margin-bottom: 30px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +.page-header h1 { + color: #333; + font-size: 32px; + font-weight: 700; + margin-bottom: 10px; +} + +.page-header .breadcrumb { + color: #888; + font-size: 14px; +} + +.page-header .breadcrumb a { + color: #667eea; + text-decoration: none; +} + +.page-header .breadcrumb a:hover { + text-decoration: underline; +} + +.section { + background: white; + padding: 30px; + border-radius: 15px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + margin-bottom: 30px; +} + +.section h2 { + color: #667eea; + font-size: 24px; + font-weight: 700; + margin-top: 0; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 3px solid #667eea; + display: flex; + align-items: center; + gap: 10px; +} + +.section h2 i { + font-size: 20px; +} + +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 12px 24px; + border: none; + border-radius: 10px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + text-decoration: none; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); +} + +.btn:active { + transform: translateY(0); +} + +.btn-secondary { + background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%); + box-shadow: 0 4px 15px rgba(127, 140, 141, 0.4); +} + +.btn-secondary:hover { + box-shadow: 0 6px 20px rgba(127, 140, 141, 0.6); +} + +.btn-sm { + padding: 8px 16px; + font-size: 14px; +} + +.alert { + padding: 15px 20px; + border-radius: 10px; + margin-bottom: 20px; + font-weight: 500; +} + +.alert-success { + background: linear-gradient(135deg, #00b894 0%, #00cec9 100%); + color: white; + box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3); +} + +.alert-error { + background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); + color: white; + box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3); +} + +.footer { + text-align: center; + color: white; + padding: 30px; + margin-top: 30px; +} + +.footer a { + color: white; + text-decoration: none; + font-weight: 500; +} + +.footer a:hover { + text-decoration: underline; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background: white; + min-width: 200px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); + border-radius: 10px; + padding: 10px 0; + z-index: 1000; + top: 100%; + margin-top: 10px; +} + +.dropdown-content a { + display: block; + padding: 12px 20px; + color: #555; + text-decoration: none; + transition: all 0.2s ease; +} + +.dropdown-content a:hover { + background: #667eea; + color: white; +} + +.dropdown-content a:first-child { + border-radius: 10px 10px 0 0; +} + +.dropdown-content a:last-child { + border-radius: 0 0 10px 10px; +} + +.dropdown-content button:hover { + background: #667eea; + color: white; +} + +.dropdown:hover .dropdown-content, +.dropdown:focus-within .dropdown-content { + display: block; +} + +.dropdown-btn { + background: none; + border: none; + color: #667eea; + font-weight: 600; + font-size: 15px; + cursor: pointer; + padding: 8px 16px; + border-radius: 8px; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.dropdown-btn:hover { + background: #667eea; + color: white; +} + +@media (max-width: 768px) { + .dropdown-content { + position: static; + box-shadow: none; + border-radius: 0; + margin-top: 0; + padding: 0; + } + + .dropdown-content a { + width: 100%; + padding: 12px 16px; + border-radius: 0; + } + + .dropdown-btn { + width: 100%; + justify-content: flex-start; + } +} diff --git a/boxes/static/css/edit_thing.css b/boxes/static/css/edit_thing.css new file mode 100644 index 0000000..d13ff5c --- /dev/null +++ b/boxes/static/css/edit_thing.css @@ -0,0 +1,21 @@ +#id_name, #id_description { + width: 100%; + padding: 10px 15px; + border: 2px solid #e0e0e0; + border-radius: 8px; + font-size: 15px; + background: white; + transition: all 0.3s; +} +#id_name:focus, #id_description:focus { + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); + outline: none; +} +#id_description { + min-height: 120px; + font-family: inherit; +} +.detail-row { + margin-bottom: 25px; +} diff --git a/boxes/static/css/thing_detail.css b/boxes/static/css/thing_detail.css new file mode 100644 index 0000000..a38b664 --- /dev/null +++ b/boxes/static/css/thing_detail.css @@ -0,0 +1,115 @@ +.markdown-content p { + margin: 0 0 1em 0; +} +.markdown-content p:last-child { + margin-bottom: 0; +} +.markdown-content h1, .markdown-content h2, .markdown-content h3, +.markdown-content h4, .markdown-content h5, .markdown-content h6 { + margin: 1.5em 0 0.5em 0; + color: #333; + font-weight: 600; +} +.markdown-content h1:first-child, .markdown-content h2:first-child, +.markdown-content h3:first-child { + margin-top: 0; +} +.markdown-content ul, .markdown-content ol { + margin: 0.5em 0; + padding-left: 2em; +} +.markdown-content li { + margin: 0.25em 0; +} +.markdown-content code { + background: #f4f4f4; + padding: 2px 6px; + border-radius: 4px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 0.9em; +} +.markdown-content pre { + background: #f4f4f4; + padding: 15px; + border-radius: 8px; + overflow-x: auto; + margin: 1em 0; +} +.markdown-content pre code { + background: none; + padding: 0; +} +.markdown-content blockquote { + border-left: 4px solid #667eea; + margin: 1em 0; + padding: 0.5em 1em; + background: #f8f9fa; + color: #666; +} +.markdown-content a { + color: #667eea; + text-decoration: none; +} +.markdown-content a:hover { + text-decoration: underline; +} +.markdown-content table { + border-collapse: collapse; + width: 100%; + margin: 1em 0; +} +.markdown-content th, .markdown-content td { + border: 1px solid #e0e0e0; + padding: 8px 12px; + text-align: left; +} +.markdown-content th { + background: #f8f9fa; + font-weight: 600; +} +.markdown-content hr { + border: none; + border-top: 2px solid #e0e0e0; + margin: 1.5em 0; +} + +.lightbox { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + z-index: 9999; + justify-content: center; + align-items: center; + cursor: zoom-out; +} + +.lightbox.active { + display: flex; +} + +.lightbox img { + max-width: 90%; + max-height: 90%; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); +} + +.lightbox-close { + position: absolute; + top: 20px; + right: 30px; + color: white; + font-size: 40px; + cursor: pointer; + z-index: 10000; + transition: opacity 0.2s; +} + +.lightbox-close:hover { + opacity: 0.7; +} diff --git a/boxes/templates/boxes/edit_thing.html b/boxes/templates/boxes/edit_thing.html index 659216b..7418648 100644 --- a/boxes/templates/boxes/edit_thing.html +++ b/boxes/templates/boxes/edit_thing.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load static %} {% load thumbnail %} {% load dict_extras %} @@ -286,29 +287,7 @@ {% endblock %} {% block extra_css %} - + {% endblock %} {% block extra_js %} diff --git a/boxes/templates/boxes/thing_detail.html b/boxes/templates/boxes/thing_detail.html index 83b4971..ddf7218 100644 --- a/boxes/templates/boxes/thing_detail.html +++ b/boxes/templates/boxes/thing_detail.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load static %} {% load thumbnail %} {% load dict_extras %} @@ -169,121 +170,5 @@ $(document).ready(function() { {% endblock %} {% block extra_css %} - + {% endblock %} diff --git a/labhelper/templates/base.html b/labhelper/templates/base.html index 1fa2430..ac17f8d 100644 --- a/labhelper/templates/base.html +++ b/labhelper/templates/base.html @@ -7,388 +7,8 @@ {% block title %}LabHelper{% endblock %} - + + {% block extra_css %}{% endblock %} {% block extra_head %}{% endblock %}