From 59bfd933038a0678e0addaed08b6620254f3bde7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Mon, 6 Oct 2025 12:02:25 +0200 Subject: [PATCH] Styles reorganization --- assets/app.js | 51 +- assets/styles/{ => 01-base}/fonts.css | 20 +- assets/styles/01-base/reset.css | 13 + assets/styles/01-base/spacing.css | 39 ++ assets/styles/{ => 01-base}/theme.css | 0 assets/styles/01-base/typography.css | 145 +++++ assets/styles/02-layout/header.css | 175 ++++++ assets/styles/{ => 02-layout}/layout.css | 28 +- assets/styles/{ => 03-components}/a2hs.css | 0 assets/styles/{ => 03-components}/article.css | 64 ++- assets/styles/{ => 03-components}/button.css | 6 +- assets/styles/{ => 03-components}/card.css | 29 +- assets/styles/03-components/cards-shared.css | 0 assets/styles/{ => 03-components}/form.css | 15 +- assets/styles/03-components/image-upload.css | 98 ++++ assets/styles/{ => 03-components}/modal.css | 9 +- .../styles/03-components/nostr-previews.css | 56 ++ assets/styles/{ => 03-components}/notice.css | 4 +- assets/styles/{ => 03-components}/og.css | 4 +- assets/styles/03-components/picture-event.css | 0 assets/styles/03-components/search.css | 21 + assets/styles/{ => 03-components}/spinner.css | 11 +- assets/styles/04-pages/admin.css | 193 +++++++ assets/styles/{ => 04-pages}/analytics.css | 8 +- assets/styles/04-pages/author-media.css | 123 +++++ assets/styles/{ => 04-pages}/landing.css | 11 +- .../styles/{ => 05-utilities}/utilities.css | 15 +- assets/styles/app.css | 520 ------------------ assets/styles/app.scss | 3 - assets/styles/components/_nostr_previews.scss | 52 -- src/Controller/AuthorController.php | 47 ++ src/Controller/EventController.php | 2 +- src/Service/NostrClient.php | 30 + templates/admin/analytics.html.twig | 2 +- templates/admin/articles.html.twig | 9 +- templates/admin/magazine_editor.html.twig | 29 +- templates/admin/magazines.html.twig | 18 +- .../components/SearchComponent.html.twig | 4 +- templates/event/_kind20_picture.html.twig | 142 +++++ templates/event/index.html.twig | 207 ++++++- templates/pages/article.html.twig | 2 - templates/pages/author-media.html.twig | 92 ++++ templates/pages/author.html.twig | 5 + templates/pages/editor.html.twig | 14 +- 44 files changed, 1613 insertions(+), 703 deletions(-) rename assets/styles/{ => 01-base}/fonts.css (63%) create mode 100644 assets/styles/01-base/reset.css create mode 100644 assets/styles/01-base/spacing.css rename assets/styles/{ => 01-base}/theme.css (100%) create mode 100644 assets/styles/01-base/typography.css create mode 100644 assets/styles/02-layout/header.css rename assets/styles/{ => 02-layout}/layout.css (90%) rename assets/styles/{ => 03-components}/a2hs.css (100%) rename assets/styles/{ => 03-components}/article.css (58%) rename assets/styles/{ => 03-components}/button.css (88%) rename assets/styles/{ => 03-components}/card.css (66%) create mode 100644 assets/styles/03-components/cards-shared.css rename assets/styles/{ => 03-components}/form.css (84%) create mode 100644 assets/styles/03-components/image-upload.css rename assets/styles/{ => 03-components}/modal.css (83%) create mode 100644 assets/styles/03-components/nostr-previews.css rename assets/styles/{ => 03-components}/notice.css (69%) rename assets/styles/{ => 03-components}/og.css (68%) create mode 100644 assets/styles/03-components/picture-event.css create mode 100644 assets/styles/03-components/search.css rename assets/styles/{ => 03-components}/spinner.css (75%) create mode 100644 assets/styles/04-pages/admin.css rename assets/styles/{ => 04-pages}/analytics.css (80%) create mode 100644 assets/styles/04-pages/author-media.css rename assets/styles/{ => 04-pages}/landing.css (85%) rename assets/styles/{ => 05-utilities}/utilities.css (89%) delete mode 100644 assets/styles/app.css delete mode 100644 assets/styles/app.scss delete mode 100644 assets/styles/components/_nostr_previews.scss create mode 100644 templates/event/_kind20_picture.html.twig create mode 100644 templates/pages/author-media.html.twig diff --git a/assets/app.js b/assets/app.js index 41640e5..a75c28f 100644 --- a/assets/app.js +++ b/assets/app.js @@ -5,22 +5,41 @@ import './bootstrap.js'; * This file will be included onto the page via the importmap() Twig function, * which should already be in your base.html.twig. */ -import './styles/fonts.css'; -import './styles/theme.css'; -import './styles/app.css'; -import './styles/layout.css'; -import './styles/button.css'; -import './styles/card.css'; -import './styles/article.css'; -import './styles/og.css'; -import './styles/form.css'; -import './styles/notice.css'; -import './styles/spinner.css'; -import './styles/a2hs.css'; -import './styles/analytics.css'; -import './styles/modal.css'; -import './styles/utilities.css'; -import './styles/landing.css'; +// 01 - Base styles (theme, fonts, typography, reset) +import './styles/01-base/fonts.css'; +import './styles/01-base/theme.css'; +import './styles/01-base/spacing.css'; +import './styles/01-base/reset.css'; +import './styles/01-base/typography.css'; + +// 02 - Layout (grid, header, navigation, main content) +import './styles/02-layout/layout.css'; +import './styles/02-layout/header.css'; + +// 03 - Components (reusable UI components) +import './styles/03-components/button.css'; +import './styles/03-components/cards-shared.css'; +import './styles/03-components/card.css'; +import './styles/03-components/form.css'; +import './styles/03-components/article.css'; +import './styles/03-components/modal.css'; +import './styles/03-components/notice.css'; +import './styles/03-components/spinner.css'; +import './styles/03-components/a2hs.css'; +import './styles/03-components/og.css'; +import './styles/03-components/nostr-previews.css'; +import './styles/03-components/picture-event.css'; +import './styles/03-components/search.css'; +import './styles/03-components/image-upload.css'; + +// 04 - Page-specific styles +import './styles/04-pages/landing.css'; +import './styles/04-pages/admin.css'; +import './styles/04-pages/analytics.css'; +import './styles/04-pages/author-media.css'; + +// 05 - Utilities (last for highest specificity) +import './styles/05-utilities/utilities.css'; console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/assets/styles/fonts.css b/assets/styles/01-base/fonts.css similarity index 63% rename from assets/styles/fonts.css rename to assets/styles/01-base/fonts.css index df52e2d..417a3aa 100644 --- a/assets/styles/fonts.css +++ b/assets/styles/01-base/fonts.css @@ -4,7 +4,7 @@ font-family: 'EB Garamond'; font-style: normal; font-weight: 400; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-italic - latin_latin-ext */ @font-face { @@ -12,7 +12,7 @@ font-family: 'EB Garamond'; font-style: italic; font-weight: 400; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-500 - latin_latin-ext */ @font-face { @@ -20,7 +20,7 @@ font-family: 'EB Garamond'; font-style: normal; font-weight: 500; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-500italic - latin_latin-ext */ @font-face { @@ -28,7 +28,7 @@ font-family: 'EB Garamond'; font-style: italic; font-weight: 500; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-600 - latin_latin-ext */ @font-face { @@ -36,7 +36,7 @@ font-family: 'EB Garamond'; font-style: normal; font-weight: 600; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-600italic - latin_latin-ext */ @font-face { @@ -44,7 +44,7 @@ font-family: 'EB Garamond'; font-style: italic; font-weight: 600; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-700 - latin_latin-ext */ @font-face { @@ -52,7 +52,7 @@ font-family: 'EB Garamond'; font-style: normal; font-weight: 700; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-700italic - latin_latin-ext */ @font-face { @@ -60,7 +60,7 @@ font-family: 'EB Garamond'; font-style: italic; font-weight: 700; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-800 - latin_latin-ext */ @font-face { @@ -68,7 +68,7 @@ font-family: 'EB Garamond'; font-style: normal; font-weight: 800; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } /* eb-garamond-800italic - latin_latin-ext */ @font-face { @@ -76,5 +76,5 @@ font-family: 'EB Garamond'; font-style: italic; font-weight: 800; - src: url('../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ + src: url('../../fonts/eb-garamond/eb-garamond-v30-latin_latin-ext-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */ } diff --git a/assets/styles/01-base/reset.css b/assets/styles/01-base/reset.css new file mode 100644 index 0000000..ca4faa1 --- /dev/null +++ b/assets/styles/01-base/reset.css @@ -0,0 +1,13 @@ +/** + * Base Reset + * Minimal reset and base element styles + */ + +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; +} diff --git a/assets/styles/01-base/spacing.css b/assets/styles/01-base/spacing.css new file mode 100644 index 0000000..99d8178 --- /dev/null +++ b/assets/styles/01-base/spacing.css @@ -0,0 +1,39 @@ +/** + * Spacing System + * Define consistent spacing variables based on an 8px base unit + * This creates a predictable rhythm across the entire application + */ + +:root { + /* Base spacing unit: 8px */ + --spacing-base: 0.5rem; /* 8px */ + + /* Spacing scale (multiples of base unit) */ + --spacing-0: 0; + --spacing-1: 0.25rem; /* 4px - micro spacing */ + --spacing-2: 0.5rem; /* 8px - small spacing */ + --spacing-3: 1rem; /* 16px - medium spacing */ + --spacing-4: 1.5rem; /* 24px - large spacing */ + --spacing-5: 2rem; /* 32px - xl spacing */ + --spacing-6: 3rem; /* 48px - xxl spacing */ + --spacing-7: 4rem; /* 64px - huge spacing */ + --spacing-8: 6rem; /* 96px - massive spacing */ + + /* Common use-case aliases */ + --spacing-xs: var(--spacing-1); + --spacing-sm: var(--spacing-2); + --spacing-md: var(--spacing-3); + --spacing-lg: var(--spacing-4); + --spacing-xl: var(--spacing-5); + --spacing-2xl: var(--spacing-6); + --spacing-3xl: var(--spacing-7); + --spacing-4xl: var(--spacing-8); + + /* Component-specific spacing */ + --button-padding-y: var(--spacing-2); + --button-padding-x: var(--spacing-4); + --card-padding: var(--spacing-4); + --input-padding: var(--spacing-2); + --section-spacing: var(--spacing-6); +} + diff --git a/assets/styles/theme.css b/assets/styles/01-base/theme.css similarity index 100% rename from assets/styles/theme.css rename to assets/styles/01-base/theme.css diff --git a/assets/styles/01-base/typography.css b/assets/styles/01-base/typography.css new file mode 100644 index 0000000..a101282 --- /dev/null +++ b/assets/styles/01-base/typography.css @@ -0,0 +1,145 @@ +/** + * Typography Styles + * Base typography including headings, paragraphs, links, and text utilities + */ + +body { + display: flex; + flex-direction: column; + min-height: 100vh; + max-width: 100%; + background-color: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family), sans-serif; + margin: 0; + padding: 0; + line-height: 1.6; +} + +/* Headings */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--heading-font), serif; + font-weight: 600; + line-height: 1.1; + color: var(--color-primary); + margin: 30px 0 10px; +} + +h1 { + font-size: 3.2rem; + margin-top: 0.25em; + font-weight: 300; +} + +h1.brand { + font-family: var(--brand-font), serif; + color: var(--color-primary); + font-size: 3.2rem; +} + +h1.brand a { + color: var(--brand-color); + text-decoration: none; +} + +h1.brand a:hover { + text-decoration: none; +} + +h1:not(.brand) > a:hover { + text-decoration: none; + font-weight: 500; +} + +h2 { + font-size: 2.2rem; +} + +h2.brand { + font-family: var(--brand-font), serif; + color: var(--color-primary); +} + +h3 { + font-size: 2rem; +} + +h4 { + font-size: 1.9rem; +} + +h5 { + font-size: 1.75rem; +} + +h6 { + font-size: 1.5rem; +} + +/* Sidebar heading overrides */ +aside h1 { + font-size: 1.2rem; +} + +aside h2 { + font-size: 1.1rem; +} + +aside p.lede { + font-size: 1rem; +} + +/* Paragraphs and text */ +p { + margin: 0 0 15px; +} + +.lede { + font-family: var(--main-body-font), serif; + font-size: 1.6rem; + word-wrap: break-word; + font-weight: 300; +} + +strong:not(>h2), .strong { + color: var(--color-primary); +} + +/* Links */ +a { + color: var(--color-secondary); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* Images and icons */ +img { + max-width: 100%; + height: auto; +} + +svg.icon { + width: 2em; + height: 2em; +} + +/* Utility classes */ +.hidden { + display: none; +} + +.divider { + border: 2px solid var(--color-primary); + margin: 20px 0; +} + +.truncate { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/assets/styles/02-layout/header.css b/assets/styles/02-layout/header.css new file mode 100644 index 0000000..18dabd2 --- /dev/null +++ b/assets/styles/02-layout/header.css @@ -0,0 +1,175 @@ +/** + * Header Component + * Main site header with navigation categories + */ + +.header { + text-align: center; + z-index: 1000; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + background-color: var(--color-bg); + border-bottom: 1px solid var(--color-border); +} + +.header .container { + display: flex; + flex-direction: column; +} + +.header__categories ul { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; + padding: 0; +} + +.header__categories li { + list-style: none; +} + +.header__categories li a:hover { + text-decoration: none; +} +/** + * Shared Card Styles + * Styles that are used across different card types in the app + */ + +.card { + background-color: var(--color-bg); + color: var(--color-text); + padding: 0; + margin: 0 0 2rem 0; + border-radius: 0; /* Sharp edges */ +} + +.card a:hover { + text-decoration: none; + color: var(--color-text); + cursor: pointer; +} + +.card a:hover h2 { + color: var(--color-text); +} + +.card.bordered { + border: 2px solid var(--color-border); +} + +.card-header { + margin: 10px 0; +} + +.header__image { + position: relative; + width: 100%; + overflow: hidden; +} + +.header__image::before { + content: ""; + display: block; + padding-top: 56.25%; /* 16:9 aspect ratio */ +} + +.header__image img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.card-body { + font-size: 1rem; +} + +.card-footer { + border-top: 1px solid var(--color-border); + margin: 20px 0; +} + +/* Featured cards layout */ +.featured-cat { + border-bottom: 2px solid var(--color-border); + padding-left: 10px; +} + +.featured-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.featured-list > * { + box-sizing: border-box; + margin-bottom: 10px; + padding: 10px; +} + +div:nth-child(odd) .featured-list { + flex-direction: row-reverse; +} + +.featured-list div:first-child { + flex: 0 0 66%; +} + +.featured-list div:last-child { + flex: 0 0 34%; +} + +.featured-list h2.card-title { + font-size: 1.5rem; +} + +.featured-list p.lede { + font-size: 1.4rem; +} + +.featured-list .card { + margin-bottom: 20px; +} + +.featured-list .card:not(:last-child) { + border-bottom: 1px solid var(--color-border); +} + +/* Article list cards */ +.article-list .metadata { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; +} + +.article-list .metadata p { + margin: 0; +} + +/* Responsive adjustments */ +@media (max-width: 1024px) { + .featured-list { + flex-direction: column !important; + } + + .featured-list .card-header { + margin-top: 20px; + } + + .featured-list .card { + border-bottom: 1px solid var(--color-border) !important; + } + + .featured-list > * { + margin-bottom: 10px; + padding: 0; + } +} + diff --git a/assets/styles/layout.css b/assets/styles/02-layout/layout.css similarity index 90% rename from assets/styles/layout.css rename to assets/styles/02-layout/layout.css index b054728..ebe51db 100644 --- a/assets/styles/layout.css +++ b/assets/styles/02-layout/layout.css @@ -29,7 +29,7 @@ nav, aside { } nav { - padding: 1em; + padding: var(--spacing-3); overflow-y: auto; /* Ensure the menu is scrollable if content is too long */ } @@ -47,7 +47,7 @@ nav ul { } nav li { - margin: 0.5em 0; + margin: var(--spacing-2) 0; } nav a { @@ -77,7 +77,7 @@ main { flex-direction: column; margin-top: 90px; flex-grow: 1; - padding: 0 1em; + padding: 0 var(--spacing-3); word-break: break-word; } @@ -94,7 +94,7 @@ main.static { width: 100%; display: flex; flex-direction: column; - gap: 1rem; + gap: var(--spacing-3); } .user-menu { @@ -240,17 +240,29 @@ footer { background-color: #333; color: white; text-align: center; - padding: 1em 0; + padding: var(--spacing-3) 0; position: relative; width: 100%; } footer .footer-links { - margin: 24px 0; + margin: var(--spacing-4) 0; } -.search input { - flex-grow: 1; +footer a { + color: var(--color-accent, #8FCB7E); + text-decoration: none; + transition: color 0.2s ease; +} + +footer a:hover { + color: white; + text-decoration: underline; +} + +footer p { + margin: var(--spacing-2) 0; + color: rgba(255, 255, 255, 0.8); } nav > header, aside > header { /* prevent global header fixed rules applying to nested headers */ diff --git a/assets/styles/a2hs.css b/assets/styles/03-components/a2hs.css similarity index 100% rename from assets/styles/a2hs.css rename to assets/styles/03-components/a2hs.css diff --git a/assets/styles/article.css b/assets/styles/03-components/article.css similarity index 58% rename from assets/styles/article.css rename to assets/styles/03-components/article.css index 7f70aae..6216a65 100644 --- a/assets/styles/article.css +++ b/assets/styles/03-components/article.css @@ -1,17 +1,22 @@ +/** + * Article Component + * Article-specific styling (content, actions, metadata) + */ + .article-main { - margin-top: 30px; + margin-top: var(--spacing-5); } .article-main h2, .article-main h3, .article-main h4, .article-main h5, .article-main h6 { - margin-top: 2em; + margin-top: var(--spacing-5); } .article-actions { display: flex; justify-content: flex-start; - gap: 1rem; - margin: 1rem 0; + gap: var(--spacing-3); + margin: var(--spacing-3) 0; } .article-main p, @@ -38,28 +43,28 @@ display: flex; justify-content: space-between; align-items: baseline; - margin: 2rem 0; - padding-top: 0.5rem; + margin: var(--spacing-5) 0; + padding-top: var(--spacing-2); border-top: 1px solid var(--color-border); font-size: 1rem; } blockquote { border-left: 6px solid var(--color-bg-light); - padding-left: 3px; - margin: 50px 0 50px 3px; + padding-left: var(--spacing-1); + margin: var(--spacing-6) 0 var(--spacing-6) var(--spacing-1); } blockquote p { font-size: 1.6rem; font-style: italic; color: var(--color-text-mid); - padding-left: 30px; + padding-left: var(--spacing-5); } .table-of-contents { border-left: var(--color-secondary) 6px solid; - margin: 2em 0; + margin: var(--spacing-5) 0; } .table-of-contents li { @@ -70,7 +75,7 @@ blockquote p { .heading-permalink { float: left; padding-right: 0; - margin-left: -30px; + margin-left: calc(var(--spacing-5) * -1); line-height: 1.2; color: var(--color-secondary); } @@ -111,3 +116,40 @@ blockquote p { .ql-snow .ql-tooltip.ql-image-tooltip::before { content: 'Image:'; } + +/* Article tags/topics */ +.tags { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-2); + margin: var(--spacing-4) 0; +} + +.tag { + display: inline-block; + padding: var(--spacing-1) var(--spacing-3); + background-color: var(--color-primary); + color: var(--color-text-contrast); + border-radius: 1.5rem; + font-size: 0.875rem; + font-weight: 500; + text-decoration: none; + transition: background-color 0.2s ease, transform 0.2s ease; +} + +.tag:hover { + background-color: var(--color-secondary); + transform: translateY(-1px); + text-decoration: none; +} + +/* Hashtag styling (for inline hashtags in content) */ +.hashtag { + color: var(--color-secondary); + font-weight: 500; +} + +.hashtag:hover { + color: var(--color-primary); + text-decoration: underline; +} diff --git a/assets/styles/button.css b/assets/styles/03-components/button.css similarity index 88% rename from assets/styles/button.css rename to assets/styles/03-components/button.css index b2141bd..722c713 100644 --- a/assets/styles/button.css +++ b/assets/styles/03-components/button.css @@ -1,9 +1,13 @@ +/** + * Button Component + * Primary button styles and variants + */ button, .btn, a.btn { background-color: var(--color-primary); color: var(--color-text-contrast); border: 2px solid var(--color-primary); - padding: 10px 20px; + padding: var(--button-padding-y) var(--button-padding-x); text-transform: uppercase; font-weight: bold; cursor: pointer; diff --git a/assets/styles/card.css b/assets/styles/03-components/card.css similarity index 66% rename from assets/styles/card.css rename to assets/styles/03-components/card.css index 18b9b27..218babd 100644 --- a/assets/styles/card.css +++ b/assets/styles/03-components/card.css @@ -1,11 +1,17 @@ +/** + * Card Component + * Specific card implementations (price lists, comments, etc.) + * For shared card styles, see cards-shared.css + */ + h2.card-title { - margin-top: 10px; + margin-top: var(--spacing-2); } .price-list { display: flex; flex-direction: row; - gap: 20px; + gap: var(--spacing-4); } .price-list .card { @@ -14,17 +20,24 @@ h2.card-title { justify-content: start; border: var(--color-primary) solid 1px; flex-grow: 1; - padding: 20px; + padding: var(--spacing-4); flex-basis: 300px; } .price-list .card .features { - list-style: none; padding: 0; + list-style: none; + padding: 0; } -.price { font-size: 22px; font-weight: bold; color: var(--color-secondary); } +.price { + font-size: 22px; + font-weight: bold; + color: var(--color-secondary); +} -.price-list .card .features li { padding: 8px 0; } +.price-list .card .features li { + padding: var(--spacing-2) 0; +} .price-list .card button:last-child { margin-top: auto; @@ -51,7 +64,7 @@ h2.card-title { display: flex; flex-direction: column; background-color: var(--color-bg-light); - padding: 10px; + padding: var(--spacing-2); } .card.comment.zap-comment { @@ -62,7 +75,7 @@ h2.card-title { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 10px; + margin-bottom: var(--spacing-2); } .card.comment .metadata p { diff --git a/assets/styles/03-components/cards-shared.css b/assets/styles/03-components/cards-shared.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/styles/form.css b/assets/styles/03-components/form.css similarity index 84% rename from assets/styles/form.css rename to assets/styles/03-components/form.css index 163b3fc..7486941 100644 --- a/assets/styles/form.css +++ b/assets/styles/03-components/form.css @@ -1,14 +1,19 @@ +/** + * Form Component + * Form input styles and layout + */ + form { display: flex; flex-direction: column; clear: both; - margin-bottom: 1em; + margin-bottom: var(--spacing-3); } form > div:not(.actions) { display: flex; flex-direction: column; - margin-bottom: 1.5em; + margin-bottom: var(--spacing-4); } label { @@ -30,7 +35,7 @@ input, textarea, select, .quill { } input, textarea, select { - padding: 10px; + padding: var(--input-padding); } textarea, input, select { @@ -65,7 +70,7 @@ input:focus, textarea:focus, select:focus { .image-with-preview img.avatar { width: 40px; height: 40px; - margin-right: 1em; + margin-right: var(--spacing-3); } .image-with-preview input { @@ -86,7 +91,7 @@ fieldset { } .actions { - margin-bottom: 1.5em; + margin-bottom: var(--spacing-4); } form ul.list-unstyled li > div > label { diff --git a/assets/styles/03-components/image-upload.css b/assets/styles/03-components/image-upload.css new file mode 100644 index 0000000..8ee2d93 --- /dev/null +++ b/assets/styles/03-components/image-upload.css @@ -0,0 +1,98 @@ +/* Image Upload Dialog Styles */ +.iu-dialog { + display: none; +} + +.iu-dialog.active { + display: block; +} + +.iu-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; +} + +.iu-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--color-bg, #fff); + border-radius: 0.5rem; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.3); + max-width: 90%; + width: 500px; + max-height: 90vh; + overflow-y: auto; + z-index: 1001; +} + +.iu-modal .modal-header { + padding: var(--spacing-3); + border-bottom: 1px solid var(--color-border, #dee2e6); + display: flex; + justify-content: space-between; + align-items: center; +} + +.iu-modal .modal-header h5 { + margin: 0; +} + +.iu-modal .modal-header .close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--color-text); +} + +.iu-modal .modal-body { + padding: var(--spacing-3); +} + +.iu-modal .modal-body > div { + margin-bottom: var(--spacing-3); +} + +.upload-area { + border: 2px dashed #ccc; + padding: var(--spacing-5); + text-align: center; + cursor: pointer; + min-height: 4em; + border-radius: 0.25rem; + transition: border-color 0.2s; +} + +.upload-area:hover { + border-color: var(--color-primary, #007bff); +} + +.upload-area input[type="file"] { + display: none; +} + +.upload-progress { + display: none; + margin-top: 1em; +} + +.upload-progress.active { + display: block; +} + +.upload-error { + color: red; + margin-top: 1em; + display: none; +} + +.upload-error.active { + display: block; +} diff --git a/assets/styles/modal.css b/assets/styles/03-components/modal.css similarity index 83% rename from assets/styles/modal.css rename to assets/styles/03-components/modal.css index 758f5e2..2b630d1 100644 --- a/assets/styles/modal.css +++ b/assets/styles/03-components/modal.css @@ -4,6 +4,7 @@ background: rgba(0, 0, 0, 0.5); z-index: 999; } + .iu-modal { position: fixed; top: 50%; @@ -13,16 +14,18 @@ width: min(600px, 90vw); max-height: 85vh; overflow: auto; - padding: 1rem 1.25rem; + padding: var(--spacing-3) var(--spacing-4); z-index: 1000; } + .iu-modal .modal-header { display: flex; justify-content: space-between; align-items: center; - gap: .5rem; - margin-bottom: .5rem; + gap: var(--spacing-2); + margin-bottom: var(--spacing-2); } + .iu-modal .close { appearance: none; border: 0; diff --git a/assets/styles/03-components/nostr-previews.css b/assets/styles/03-components/nostr-previews.css new file mode 100644 index 0000000..22605bc --- /dev/null +++ b/assets/styles/03-components/nostr-previews.css @@ -0,0 +1,56 @@ +/** + * Nostr Previews Component + * Styles for Nostr event and profile preview cards + * Converted from SCSS to plain CSS + */ + +.nostr-preview { + margin-top: var(--spacing-2); +} + +.nostr-preview .nostr-event-preview, +.nostr-preview .nostr-profile-preview { + border-left: 3px solid #6c5ce7; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.nostr-preview .nostr-profile-preview { + border-left-color: #00b894; +} + +.nostr-preview .card-title { + margin-bottom: var(--spacing-2); + font-size: 1rem; +} + +.nostr-preview .card-text { + font-size: 0.9rem; +} + +.nostr-preview .card-footer { + padding: var(--spacing-2) var(--spacing-3); +} + +.nostr-previews h6 { + font-size: 0.9rem; + margin-bottom: var(--spacing-3); +} + +.nostr-previews .preview-container { + max-height: 500px; + overflow-y: auto; + padding-right: var(--spacing-2); +} + +/* Style for nostr links in text */ +.nostr-link { + color: #6c5ce7; + background-color: rgba(108, 92, 231, 0.1); + padding: 0 var(--spacing-1); + border-radius: 3px; + text-decoration: none; +} + +.nostr-link:hover { + background-color: rgba(108, 92, 231, 0.2); +} diff --git a/assets/styles/notice.css b/assets/styles/03-components/notice.css similarity index 69% rename from assets/styles/notice.css rename to assets/styles/03-components/notice.css index 7ecc1e3..912ac59 100644 --- a/assets/styles/notice.css +++ b/assets/styles/03-components/notice.css @@ -1,7 +1,7 @@ .notice { - padding: 10px; /* Padding around the content */ + padding: var(--spacing-2); /* Padding around the content */ border-radius: 5px; /* Rounded corners */ - margin: 10px 0; /* Margin above and below the notice */ + margin: var(--spacing-2) 0; /* Margin above and below the notice */ } .notice p { diff --git a/assets/styles/og.css b/assets/styles/03-components/og.css similarity index 68% rename from assets/styles/og.css rename to assets/styles/03-components/og.css index d5985ef..b5ad2f2 100644 --- a/assets/styles/og.css +++ b/assets/styles/03-components/og.css @@ -1,7 +1,7 @@ .og-preview-card { max-width: 100%; - padding: 20px; - margin: 10px 0; + padding: var(--spacing-4); + margin: var(--spacing-2) 0; background-color: var(--color-bg); } diff --git a/assets/styles/03-components/picture-event.css b/assets/styles/03-components/picture-event.css new file mode 100644 index 0000000..e69de29 diff --git a/assets/styles/03-components/search.css b/assets/styles/03-components/search.css new file mode 100644 index 0000000..d3c594f --- /dev/null +++ b/assets/styles/03-components/search.css @@ -0,0 +1,21 @@ +/* Search Component Styles */ +.search-component { + margin-bottom: 1rem; +} + +.search-credits { + text-align: right; +} + +.search-credits .help-text { + font-size: 0.875rem; +} + +.search-loading { + text-align: center; +} + +.search form { + margin-bottom: 1rem; +} + diff --git a/assets/styles/spinner.css b/assets/styles/03-components/spinner.css similarity index 75% rename from assets/styles/spinner.css rename to assets/styles/03-components/spinner.css index a92ccdd..ebea506 100644 --- a/assets/styles/spinner.css +++ b/assets/styles/03-components/spinner.css @@ -1,8 +1,14 @@ +/** + * Spinner Component + * Main loading spinner (dual-ring design) + * Use this for full-page or section loading states + */ + .spinner { display: flex; justify-content: center; align-items: center; - margin: 1em 0; + margin: var(--spacing-3) 0; } .lds-dual-ring { @@ -10,12 +16,13 @@ width: 30px; height: 30px; } + .lds-dual-ring:after { content: " "; display: block; width: 32px; height: 32px; - margin: 4px; + margin: var(--spacing-1); border-radius: 50%; border: 4px solid var(--color-primary); border-color: var(--color-primary) transparent var(--color-primary) transparent; diff --git a/assets/styles/04-pages/admin.css b/assets/styles/04-pages/admin.css new file mode 100644 index 0000000..cb678d3 --- /dev/null +++ b/assets/styles/04-pages/admin.css @@ -0,0 +1,193 @@ +/* Admin Panel Styles */ + +/* Analytics */ +.analytics-container { + padding: var(--spacing-3); +} + +.analytics-card { + margin-bottom: var(--spacing-5); + padding: var(--spacing-3); + background-color: var(--color-card-bg, #f8f9fa); + border-radius: 0.5rem; +} + +.analytics-stats { + list-style: none; + padding: 0; + margin: 0; +} + +.analytics-stats li { + padding: var(--spacing-2) 0; +} + +.analytics-table { + width: 100%; + border-collapse: collapse; +} + +.analytics-table th { + padding: var(--spacing-3); + text-align: left; + border-bottom: 2px solid var(--color-border, #dee2e6); +} + +.analytics-table th.text-right, +.analytics-table th[style*="text-align: right"] { + min-width: 100px; + text-align: right; +} + +.analytics-table td { + padding: var(--spacing-3); + border-bottom: 1px solid var(--color-border, #dee2e6); +} + +.analytics-table td.text-right { + text-align: right; +} + +.analytics-info { + padding: var(--spacing-3); + background-color: var(--color-info-bg, #e9f5ff); + border-radius: 0.25rem; +} + +/* Articles Table */ +.admin-articles-table { + width: 100%; + border-collapse: collapse; +} + +.admin-articles-table tr { + padding-bottom: var(--spacing-1); +} + +.admin-articles-table td { + padding: var(--spacing-2); + vertical-align: top; +} + +.admin-articles-table button { + margin-left: var(--spacing-2); +} + +.admin-articles-table form { + display: inline; +} + +/* Magazine Editor */ +.magazine-editor-layout { + display: flex; + gap: var(--spacing-4); +} + +.magazine-editor-section { + flex: 1; + min-width: 320px; +} + +.magazine-search-form { + margin-bottom: var(--spacing-3); + display: flex; + gap: var(--spacing-2); +} + +.magazine-search-form input[type="text"] { + flex: 1; +} + +.magazine-table { + width: 100%; + border-collapse: collapse; +} + +.magazine-table th { + padding: 0.25rem 0.5rem; + text-align: left; + border-bottom: 1px solid var(--color-border, #dee2e6); +} + +.magazine-table td { + padding: 0.25rem 0.5rem; + vertical-align: top; +} + +.magazine-table td.actions { + text-align: right; +} + +.magazine-table td.author { + white-space: nowrap; +} +/* NIP-68 Picture Event Styles */ +.picture-event { + margin: 1rem 0; +} + +.picture-title { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.content-warning { + padding: 1rem; + background-color: var(--color-warning-bg, #fff3cd); + border: 1px solid var(--color-warning-border, #ffc107); + border-radius: 0.25rem; + margin-bottom: 1rem; +} + +.btn-show-nsfw { + margin-left: 0.5rem; + padding: 0.25rem 0.5rem; + background-color: var(--color-primary); + color: white; + border: none; + border-radius: 0.25rem; + cursor: pointer; +} + +.btn-show-nsfw:hover { + opacity: 0.9; +} + +.picture-gallery { + display: block; +} + +.picture-gallery.hidden { + display: none; +} + +.picture-item { + position: relative; + margin-bottom: 1rem; +} + +.picture-image { + max-width: 100%; + height: auto; + display: block; +} + +.annotated-users { + position: relative; +} + +.user-tag { + position: absolute; + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.875rem; + white-space: nowrap; +} + +.picture-alt { + margin-top: 0.5rem; + font-style: italic; + color: var(--color-text-muted, #6c757d); +} diff --git a/assets/styles/analytics.css b/assets/styles/04-pages/analytics.css similarity index 80% rename from assets/styles/analytics.css rename to assets/styles/04-pages/analytics.css index cd5586f..4464e00 100644 --- a/assets/styles/analytics.css +++ b/assets/styles/04-pages/analytics.css @@ -1,14 +1,14 @@ .analytics-container { max-width: 800px; margin: 0 auto; - padding: 20px; + padding: var(--spacing-4); } .analytics-card { background: lightgray; border-radius: 8px; - padding: 20px; - margin-bottom: 20px; + padding: var(--spacing-4); + margin-bottom: var(--spacing-4); box-shadow: 0 2px 5px rgba(0,0,0,0.1); } @@ -18,7 +18,7 @@ } .analytics-table th, .analytics-table td { - padding: 10px; + padding: var(--spacing-2); border-bottom: 1px solid var(--color-border); } diff --git a/assets/styles/04-pages/author-media.css b/assets/styles/04-pages/author-media.css new file mode 100644 index 0000000..293e5c2 --- /dev/null +++ b/assets/styles/04-pages/author-media.css @@ -0,0 +1,123 @@ +.profile-tabs { + display: flex; + gap: 1rem; + margin: 1.5rem 0; + border-bottom: 2px solid #e0e0e0; +} + +.tab-link { + padding: 0.75rem 1.5rem; + text-decoration: none; + color: #666; + border-bottom: 3px solid transparent; + margin-bottom: -2px; + transition: all 0.3s ease; +} + +.tab-link:hover { + color: #333; + border-bottom-color: #ccc; +} + +.tab-link.active { + color: #0066cc; + border-bottom-color: #0066cc; + font-weight: 600; +} + +.masonry-grid { + column-count: 3; + column-gap: 1.5rem; + margin: 2rem 0; +} + +@media (max-width: 1200px) { + .masonry-grid { + column-count: 2; + } +} + +@media (max-width: 768px) { + .masonry-grid { + column-count: 1; + } +} + +.masonry-item { + break-inside: avoid; + margin-bottom: 1.5rem; + background: #fff; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.masonry-item:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.masonry-link, .masonry-link:hover { + display: block; + text-decoration: none; + color: inherit; +} + +.masonry-image-container { + width: 100%; + overflow: hidden; + background-color: #f5f5f5; +} + +.masonry-image { + width: 100%; + height: auto; + display: block; + transition: transform 0.3s ease; +} + +.masonry-item:hover .masonry-image { + transform: scale(1.05); +} + +.masonry-caption { + padding: 1rem 1rem 0.5rem; +} + +.masonry-caption h3 { + margin: 0; + font-size: 1.1rem; + font-weight: 600; + color: #333; + line-height: 1.4; +} + +.masonry-description { + padding: 0 1rem; + font-size: 0.9rem; + color: #666; + line-height: 1.5; +} + +.masonry-meta { + padding: 0.75rem 1rem; + border-top: 1px solid #f0f0f0; + margin-top: 0.5rem; +} + +.event-date { + font-size: 0.85rem; + color: #999; +} + +.no-media { + text-align: center; + padding: 3rem 1rem; + color: #666; +} + +.no-media p { + font-size: 1.1rem; +} + diff --git a/assets/styles/landing.css b/assets/styles/04-pages/landing.css similarity index 85% rename from assets/styles/landing.css rename to assets/styles/04-pages/landing.css index 00d4629..ed632d7 100644 --- a/assets/styles/landing.css +++ b/assets/styles/04-pages/landing.css @@ -1,4 +1,3 @@ - .center{ text-align: center; } /* Eyebrow + lede */ @@ -7,12 +6,12 @@ letter-spacing: .12em; font-size: .8rem; color: var(--color-text-mid); - margin: 0 0 1rem; + margin: 0 0 var(--spacing-3); } /* Hero split */ .ln-hero{ - margin-top: 100px; + margin-top: var(--spacing-8); background: var(--color-bg); color: var(--color-text); } @@ -27,7 +26,7 @@ } /* Generic section shell */ -.ln-section{ position: relative; padding: 3.2rem 0; } +.ln-section{ position: relative; padding: var(--section-spacing) 0; } /* Split layout for features */ .ln-split{ @@ -35,9 +34,9 @@ grid-template-columns: 280px 1fr; align-items: start; } -.ln-split__aside{ position: sticky; top: 24px; align-self: start; } +.ln-split__aside{ position: sticky; top: var(--spacing-4); align-self: start; } .ln-split__body .measure{ max-width: 70ch; } -.cta-row{ margin-top: .6rem; } +.cta-row{ margin-top: var(--spacing-2); } /* Section palettes (alternating) */ .ln-section--search{ diff --git a/assets/styles/utilities.css b/assets/styles/05-utilities/utilities.css similarity index 89% rename from assets/styles/utilities.css rename to assets/styles/05-utilities/utilities.css index 8ef8f49..1cabdfa 100644 --- a/assets/styles/utilities.css +++ b/assets/styles/05-utilities/utilities.css @@ -1,4 +1,9 @@ -/* Utility classes (plain CSS) - spacing, text, layout, alerts, etc. */ +/** + * Utility Classes + * Bootstrap-style utility classes for quick styling adjustments + * Note: For main loading spinners, use .lds-dual-ring from spinner.css + * .spinner-border is for inline/button spinners + */ /* Spacing scale: 0=0, 1=.25rem, 2=.5rem, 3=1rem, 4=1.5rem, 5=3rem */ .m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important} @@ -9,13 +14,15 @@ .mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important} .my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important} .mx-auto{margin-left:auto!important;margin-right:auto!important} + /* Display & layout */ .d-flex{display:flex!important;flex-direction:column} .d-inline{display:inline!important} .d-block{display:block!important} .gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important} -.flex-row{flex-direction:row} .flex-wrap{flex-wrap: wrap} +.flex-row{flex-direction:row} +.flex-wrap{flex-wrap: wrap} .justify-content-between{justify-content:space-between!important} .justify-content-center{justify-content:center!important} .align-items-center{align-items:center!important} @@ -43,7 +50,7 @@ /* Buttons - sizes only (base buttons defined elsewhere) */ .btn.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.25;border-radius:.2rem} -/* Spinner (bootstrap-like) */ +/* Inline spinner (for buttons, etc.) */ .spinner-border{display:inline-block;width:1.5rem;height:1.5rem;border:.2em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite} .spinner-border-sm{width:1rem;height:1rem;border-width:.15em} @keyframes spinner-border{to{transform:rotate(360deg)}} @@ -52,4 +59,4 @@ details>summary{cursor:pointer} /* Text truncation with ellipsis */ -.line-clamp-5 {display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 5;overflow: hidden;text-overflow: ellipsis;} +.line-clamp-5{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:5;overflow:hidden;text-overflow:ellipsis} diff --git a/assets/styles/app.css b/assets/styles/app.css deleted file mode 100644 index 3a5097b..0000000 --- a/assets/styles/app.css +++ /dev/null @@ -1,520 +0,0 @@ -body { - display: flex; - flex-direction: column; - min-height: 100vh; - max-width: 100%; - background-color: var(--color-bg); - color: var(--color-text); - font-family: var(--font-family), sans-serif; - margin: 0; - padding: 0; - line-height: 1.6; -} - -h1, h2, h3, h4, h5, h6 { - font-family: var(--heading-font), serif; - font-weight: 600; - line-height: 1.1; - color: var(--color-primary); - margin: 30px 0 10px; -} - -h1 { - font-size: 3.2rem; - margin-top: 0.25em; - font-weight: 300; -} - -h1.brand { - font-family: var(--brand-font), serif; - color: var(--color-primary); - font-size: 3.2rem; -} - -@media screen and (max-width: 600px) { - h1.brand { - font-size: 2.3rem; - } -} - -h1.brand a { - color: var(--brand-color); -} - -h1:not(.brand) > a:hover { - text-decoration: none; - font-weight: 500; -} - -h2 { - font-size: 2.2rem; -} - -h2.brand { - font-family: var(--brand-font), serif; - color: var(--color-primary); -} - -h3 { - font-size: 2rem; -} - -h4 { - font-size: 1.9rem; -} - -h5 { - font-size: 1.75rem; -} - -h6 { - font-size: 1.5rem; -} - -p { - margin: 0 0 15px; -} - -aside h1 { - font-size: 1.2rem; -} - -aside h2 { - font-size: 1.1rem; -} - -aside p.lede { - font-size: 1rem; -} - -.lede { - font-family: var(--main-body-font), serif; - font-size: 1.6rem; - word-wrap: break-word; - font-weight: 300; -} - -strong:not(>h2), .strong { - color: var(--color-primary); -} - -.hidden { - display: none; -} - -a { - color: var(--color-secondary); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -.card a:hover { - text-decoration: none; - color: var(--color-text); - cursor: pointer; -} - -.card a:hover h2 { - color: var(--color-text); -} - -img { - max-width: 100%; - height: auto; -} - -svg.icon { - width: 2em; - height: 2em; -} - -.divider { - border: 2px solid var(--color-primary); - margin: 20px 0; -} - -.hashtag { - color: var(--color-secondary); -} - -.card { - background-color: var(--color-bg); - color: var(--color-text); - padding: 0; - margin: 0 0 2rem 0; - border-radius: 0; /* Sharp edges */ -} - -.featured-cat { - border-bottom: 2px solid var(--color-border); - padding-left: 10px; -} - -.featured-list { - display: flex; - flex-direction: row; - flex-wrap: wrap; -} - - -.featured-list > * { - box-sizing: border-box; /* so padding/border don't break the layout */ - margin-bottom: 10px; - padding: 10px; -} - -@media (max-width: 1024px) { - .featured-list { - flex-direction: column !important; - } - - .featured-list .card-header { - margin-top: 20px; - } - - .featured-list .card { - border-bottom: 1px solid var(--color-border) !important; - } - - .featured-list > * { - margin-bottom: 10px; - padding: 0; - } -} -div:nth-child(odd) .featured-list { - flex-direction: row-reverse; -} - -.featured-list div:first-child { - flex: 0 0 66%; /* each item takes up 50% width = 2 columns */ -} - -.featured-list div:last-child { - flex: 0 0 34%; /* each item takes up 50% width = 2 columns */ -} - -.featured-list h2.card-title { - font-size: 1.5rem; -} - -.featured-list p.lede { - font-size: 1.4rem; -} - -.featured-list .card { - margin-bottom: 20px; -} - -.featured-list .card:not(:last-child) { - border-bottom: 1px solid var(--color-border); -} - -.article-list .metadata { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: baseline; -} - -.article-list .metadata p { - margin: 0; -} - -.truncate { - display: -webkit-box; - -webkit-line-clamp: 3; /* limit to 3 lines */ - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -} - -.card.bordered { - border: 2px solid var(--color-border); -} - -.card-header { - margin: 10px 0; -} - -.header__image { - position: relative; - width: 100%; - overflow: hidden; /* Ensures any overflow is hidden */ -} - -.header__image::before { - content: ""; - display: block; - padding-top: 56.25%; /* 16:9 aspect ratio (9 / 16 * 100 = 56.25%) */ -} - -.header__image img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; /* Ensures the image covers the entire area while maintaining its aspect ratio */ -} - -.card-body { - font-size: 1rem; -} - -.card-footer { - border-top: 1px solid var(--color-border); - margin: 20px 0; -} - -.header { - text-align: center; - z-index: 1000; /* Ensure it stays on top */ - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - background-color: var(--color-bg); /* Black background */ - border-bottom: 1px solid var(--color-border); -} - -.header .container { - display: flex; - flex-direction: column; -} - -.header__categories ul { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 20px; - padding: 0; -} - -.header__categories li { - list-style: none; -} - -.header__categories li a:hover { - text-decoration: none; - font-weight: bold; -} - -.header__categories a.active { - font-weight: bold; -} - -.header__logo h1 { - font-weight: normal; -} - -.header__logo img { - height: 40px; /* Adjust the height as needed */ -} - -.header__logo a:hover { - text-decoration: none; -} - -.header__user { - position: relative; - display: flex; - align-items: center; -} - -.header__avatar img { - height: 40px; /* Adjust the avatar size as needed */ - width: 40px; - border-radius: 50%; - cursor: pointer; -} - -.header__dropdown { - display: none; - position: absolute; - top: 50px; /* Adjust this depending on the header.html.twig height */ - right: 0; - background-color: var(--color-text); /* White dropdown */ - border: 2px solid var(--color-bg); /* Black border */ - list-style: none; - padding: 10px 0; - z-index: 1000; - border-radius: 0; /* Sharp edges */ -} - -.header__dropdown ul { - margin: 0; - padding: 0; -} - -.header__dropdown li { - padding: 10px 20px; -} - -.header__dropdown li a { - color: var(--color-bg); /* Black text */ - text-decoration: none; -} - -.header__dropdown li a:hover { - background-color: var(--color-bg); /* Black background on hover */ - color: var(--color-text); /* White text on hover */ - display: block; -} - -footer p { - margin: 0; -} - -footer a { - color: var(--color-text-contrast); -} - -/* Tags container */ -.tags { - margin: 10px 0; - display: flex; - flex-wrap: wrap; /* Allows tags to wrap to the next line if needed */ - gap: 10px; /* Adds spacing between individual tags */ -} - -/* Individual tag */ -.tag { - background-color: var(--color-bg-light); - color: var(--color-text-mid); - padding: 3px 6px; /* Padding around the tag text */ - border-radius: 20px; /* Rounded corners (pill-shaped) */ - font-size: 0.75em; /* Slightly smaller text */ - cursor: pointer; /* Cursor turns to pointer for clickable tags */ - text-decoration: none; /* Removes any text decoration (e.g., underline) */ - display: inline-block; /* Makes sure each tag behaves like a block with padding */ - transition: background-color 0.3s ease; /* Smooth hover effect */ -} - -/*!* Hover effect for tags *!*/ -/*.tag:hover {*/ -/* color: var(--color-text-contrast);*/ -/*}*/ - -/* Optional: Responsive adjustments for smaller screens */ -@media (max-width: 768px) { - .tag { - font-size: 0.8em; /* Slightly smaller text for mobile */ - } -} - - - -.card.card__horizontal { - display: flex; - justify-content: space-between; - align-items: center; - - h1 { - font-size: 2rem; - } - - .card-content { - flex: 1; - margin-right: 30px; - padding: 0 8px; - } - - .card-image img { - width: 220px; - max-height: 220px; - object-fit: contain; - } -} - -.article__image img { - margin: 1rem 0; - width: 100%; -} - -.badge { - background-color: var(--color-primary); - color: var(--color-bg); - padding: 3px 8px; - border-radius: 20px; - font-family: var(--font-family), sans-serif; - font-weight: bold; - font-size: 0.65em; - text-transform: uppercase; - margin-right: 5px; - vertical-align: super; -} - -.badge.badge__secondary { - background-color: var(--color-secondary); -} - -.avatar { - width: 24px; /* Adjust the size as needed */ - height: 24px; /* Adjust the size as needed */ - border-radius: 50%; /* Makes the image circular */ - object-fit: cover; /* Ensures the image scales correctly */ - display: inline-block; - vertical-align: middle; -} - -.alert { - padding: 10px 20px; /* Padding around the text */ - border-radius: 5px; /* Rounded corners */ - margin: 20px 0; /* Spacing around the alert */ -} - -.alert.alert-success { - background-color: var(--color-secondary); - color: var(--color-text-contrast); -} - -/* Tabs Container */ -.nav-tabs { - display: flex; /* Arrange items in a row */ - justify-content: center; - padding: 0; /* Remove padding */ - margin: 0; /* Remove margin */ - list-style: none; /* Remove list item styling */ -} - -/* Individual Tab Item */ -.nav-tabs .nav-item { - margin: 0; /* No margin around list items */ -} - -/* NON-Active Tab */ -.nav-tabs .nav-link { - color: var(--color-text); - background-color: transparent; - border: none; -} - - -/* Active Tab */ -.nav-tabs .nav-link.active { - color: var(--color-text-contrast); - background-color: var(--color-primary); - font-weight: bold; -} - -/* Content Container */ -.tab-content { - padding: 15px; /* Spacing inside the content */ - border-top: none; /* Remove border overlap with active tab */ -} - -/* Quill editor */ -#editor { - height: 400px; - margin-bottom: 20px; -} - -/* Search */ -label.search { - width: 100%; - justify-content: center; - margin-bottom: 15px; -} diff --git a/assets/styles/app.scss b/assets/styles/app.scss deleted file mode 100644 index a313524..0000000 --- a/assets/styles/app.scss +++ /dev/null @@ -1,3 +0,0 @@ -// ...existing code... -@import "components/nostr_previews"; -// ...existing code... diff --git a/assets/styles/components/_nostr_previews.scss b/assets/styles/components/_nostr_previews.scss deleted file mode 100644 index fe3dc65..0000000 --- a/assets/styles/components/_nostr_previews.scss +++ /dev/null @@ -1,52 +0,0 @@ -.nostr-preview { - margin-top: 0.5rem; - - .nostr-event-preview, .nostr-profile-preview { - border-left: 3px solid #6c5ce7; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - - .nostr-profile-preview { - border-left-color: #00b894; - } - - .card-title { - margin-bottom: 0.5rem; - font-size: 1rem; - } - - .card-text { - font-size: 0.9rem; - } - - .card-footer { - padding: 0.5rem 1rem; - } -} - -.nostr-previews { - h6 { - font-size: 0.9rem; - margin-bottom: 1rem; - } - - .preview-container { - // For multiple previews - max-height: 500px; - overflow-y: auto; - padding-right: 10px; - } -} - -// Style for nostr links in text -.nostr-link { - color: #6c5ce7; - background-color: rgba(108, 92, 231, 0.1); - padding: 0 3px; - border-radius: 3px; - text-decoration: none; - - &:hover { - background-color: rgba(108, 92, 231, 0.2); - } -} diff --git a/src/Controller/AuthorController.php b/src/Controller/AuthorController.php index 480df6e..eaed478 100644 --- a/src/Controller/AuthorController.php +++ b/src/Controller/AuthorController.php @@ -10,12 +10,59 @@ use Elastica\Query\Terms; use Exception; use FOS\ElasticaBundle\Finder\FinderInterface; use swentel\nostr\Key\Key; +use swentel\nostr\Nip19\Nip19Helper; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class AuthorController extends AbstractController { + /** + * @throws Exception + */ + #[Route('/p/{npub}/media', name: 'author-media', requirements: ['npub' => '^npub1.*'])] + public function media($npub, NostrClient $nostrClient, RedisCacheService $redisCacheService): Response + { + $keys = new Key(); + $pubkey = $keys->convertToHex($npub); + + $author = $redisCacheService->getMetadata($npub); + + // Retrieve picture events (kind 20) for the author + try { + $pictureEvents = $nostrClient->getPictureEventsForPubkey($npub, 30); + } catch (Exception $e) { + $pictureEvents = []; + } + + // Deduplicate by event ID + $uniqueEvents = []; + foreach ($pictureEvents as $event) { + if (!isset($uniqueEvents[$event->id])) { + $uniqueEvents[$event->id] = $event; + } + } + + // Convert back to indexed array and sort by date (newest first) + $pictureEvents = array_values($uniqueEvents); + usort($pictureEvents, function ($a, $b) { + return $b->created_at <=> $a->created_at; + }); + + // Encode event IDs as note1... for each event + foreach ($pictureEvents as $event) { + $nip19 = new Nip19Helper(); // The NIP-19 helper class. + $event->noteId = $nip19->encodeNote($event->id); + } + + return $this->render('pages/author-media.html.twig', [ + 'author' => $author, + 'npub' => $npub, + 'pictureEvents' => $pictureEvents, + 'is_author_profile' => true, + ]); + } + /** * @throws Exception */ diff --git a/src/Controller/EventController.php b/src/Controller/EventController.php index 4a6a3d1..7c5aadd 100644 --- a/src/Controller/EventController.php +++ b/src/Controller/EventController.php @@ -24,7 +24,7 @@ class EventController extends AbstractController /** * @throws Exception */ - #[Route('/e/{nevent}', name: 'nevent', requirements: ['nevent' => '^nevent1.*'])] + #[Route('/e/{nevent}', name: 'nevent', requirements: ['nevent' => '^(nevent|note)1.*'])] public function index($nevent, NostrClient $nostrClient, RedisCacheService $redisCacheService, NostrLinkParser $nostrLinkParser, LoggerInterface $logger): Response { $logger->info('Accessing event page', ['nevent' => $nevent]); diff --git a/src/Service/NostrClient.php b/src/Service/NostrClient.php index 2a961b6..155fd64 100644 --- a/src/Service/NostrClient.php +++ b/src/Service/NostrClient.php @@ -516,6 +516,36 @@ class NostrClient }); } + /** + * Get picture events (kind 20) for a specific author + * @throws \Exception + */ + public function getPictureEventsForPubkey(string $ident, int $limit = 20): array + { + // Add user relays to the default set + $authorRelays = $this->getTopReputableRelaysForAuthor($ident); + // Create a RelaySet from the author's relays + $relaySet = $this->defaultRelaySet; + if (!empty($authorRelays)) { + $relaySet = $this->createRelaySet($authorRelays); + } + + // Create request for kind 20 (picture events) + $request = $this->createNostrRequest( + kinds: [20], // NIP-68 Picture events + filters: [ + 'authors' => [$ident], + 'limit' => $limit + ], + relaySet: $relaySet + ); + + // Process the response and return raw events + return $this->processResponse($request->send(), function($event) { + return $event; // Return the raw event + }); + } + public function getArticles(array $slugs): array { $articles = []; diff --git a/templates/admin/analytics.html.twig b/templates/admin/analytics.html.twig index 6f8fe1c..94f420d 100644 --- a/templates/admin/analytics.html.twig +++ b/templates/admin/analytics.html.twig @@ -22,7 +22,7 @@ Route - # + # diff --git a/templates/admin/articles.html.twig b/templates/admin/articles.html.twig index 135b8f3..a3c0f94 100644 --- a/templates/admin/articles.html.twig +++ b/templates/admin/articles.html.twig @@ -2,7 +2,7 @@ {% block body %}

Latest 50 Articles

- +
@@ -12,7 +12,7 @@ {% for article in articles %} - +
Title
{{ article.title }} {{ article.summary|slice(0, 100) }}{% if article.summary|length > 100 %}...{% endif %} @@ -20,11 +20,10 @@ + data-action="click->copy-to-clipboard#copyToClipboard">Copy to Clipboard -
+